/* メソッド **************************************************/ /// <summary> /// 2点の座標と、長半径と扁平率をセットする必要がある /// </summary> public PolygonEllipse(Blh p1, Blh p2, double a, double f) { this.focus[0] = p1; this.focus[1] = p2; this.LongRadius = a; this.f = f; }
/// <summary> /// 2地点間の距離を求めるテスト /// <para>コンソールアプリでの利用を前提としています。</para> /// <para>ソースコードのコメントにおける、Google Earthの数字はver. 6.1.0.5001にて確認したものです。</para> /// <para>Google Earthとの差は、計算アルゴリズム若しくは測地系に起因するものと考えられます。(未チェック)</para> /// </summary> public static void TwoBlhToDistanceTest() { Console.WriteLine("Test 1: 熊本高専八代キャンパスで地図上(カシミール3D)で約10 mの距離"); Blh x1 = new Blh(32.476667, 130.607132); // 八代高専で地図上(カシミール3D)で約10 mの距離 Blh x2 = new Blh("32.476757", "130.607132");// 文字列でもよい Console.WriteLine("test point is " + x1.ToString() + " and " + x2.ToString()); Console.WriteLine("GetDistance2 test result: " + x1.GetDistance2(x2).ToString()); Console.WriteLine("GetDistance3 test result: " + x1.GetDistance3(x2).ToString()); Console.WriteLine("GetDistance4 test result: " + x1.GetDistance4(x2).ToString()); Console.WriteLine("GetDistance5 test result: " + x1.GetDistance5(x2).ToString()); Console.WriteLine("GetDistance6 test result: " + x1.GetDistance6(x2).ToString()); Console.WriteLine("Test 2: 熊本高専八代キャンパス-熊本大学"); x1 = new Blh(32.476032, 130.606439); // 八代高専-熊本大学,(Google Earthにおいて)地図上39,131.83, 地上39,131.69, 方位197.02 ただし、八代高専は標高4 m、熊大は標高27 mとされていた。 x2 = new Blh(32.813365, 130.728803); Console.WriteLine("test point is " + x1.ToString() + " and " + x2.ToString()); Console.WriteLine("GetDistance2 test result: " + x1.GetDistance2(x2).ToString()); Console.WriteLine("GetDistance3 test result: " + x1.GetDistance3(x2).ToString()); Console.WriteLine("GetDistance4 test result: " + x1.GetDistance4(x2).ToString()); Console.WriteLine("GetDistance5 test result: " + x1.GetDistance5(x2).ToString()); Console.WriteLine("GetDistance6 test result: " + x1.GetDistance6(x2).ToString()); Console.WriteLine("Test 3: 熊本大学-東京海洋大学"); x1 = new Blh(32.813365, 130.728803); // 熊本大学-東京海洋大学,(Google Earthにおいて)地図上892,360.80, 地上892,362.87, 方位66.63 ただし、東京海洋大学は標高2 mとされていた。 x2 = new Blh(35.667092, 139.790611); Console.WriteLine("test point is " + x1.ToString() + " and " + x2.ToString()); Console.WriteLine("GetDistance2 test result: " + x1.GetDistance2(x2).ToString()); Console.WriteLine("GetDistance3 test result: " + x1.GetDistance3(x2).ToString()); Console.WriteLine("GetDistance4 test result: " + x1.GetDistance4(x2).ToString()); Console.WriteLine("GetDistance5 test result: " + x1.GetDistance5(x2).ToString()); Console.WriteLine("GetDistance6 test result: " + x1.GetDistance6(x2).ToString()); Console.WriteLine("Test 4: 東京海洋大学-サンフランシスコ"); x1 = new Blh(35.667092, 139.790611); // 東京海洋大学-サンフランシスコ,(Google Earthにおいて)地図上8,283,703.36, 地上8,283,715.04, 方位55.43 ただし、サンフランシスコは標高10 mとされていた。 x2 = new Blh(37.774931, -122.419414); Console.WriteLine("test point is " + x1.ToString() + " and " + x2.ToString()); Console.WriteLine("GetDistance2 test result: " + x1.GetDistance2(x2).ToString()); Console.WriteLine("GetDistance3 test result: " + x1.GetDistance3(x2).ToString()); Console.WriteLine("GetDistance4 test result: " + x1.GetDistance4(x2).ToString()); Console.WriteLine("GetDistance5 test result: " + x1.GetDistance5(x2).ToString()); Console.WriteLine("GetDistance6 test result: " + x1.GetDistance6(x2).ToString()); return; }
/// <summary> /// 測位座標情報をコピーする /// </summary> /// <returns>測位座標情報</returns> public Blh[] GetPositions() { Blh[] ans = new Blh[data.Count]; for (int i = 0; i < data.Count; i++) ans[i] = data[i].Position; return ans; }
// もう少しマッチの条件をスマートに書きたいんだけどなぁ。 /* メソッド *****************************************************/ /// <summary> /// 引数で渡されたテキストを走査してDEM情報クラスオブジェクトを返す /// <para>フォーマット不一致の場合はnew Info()を返します。</para> /// </summary> /// <param name="fileName">解析対象のファイルへのパス</param> /// <returns>正常に読み出せた場合はDEM情報クラスオブジェクトを返す</returns> public Info GetHeader(string fileName) { if (System.IO.File.Exists(fileName) == false) return new Info(); string txt = ""; using (System.IO.StreamReader sr = new System.IO.StreamReader(fileName, System.Text.Encoding.GetEncoding("shift_jis"))) { StringBuilder sb = new StringBuilder(2000); // 予め大きなメモリ容量を確保しておく for (int i = 0; i < 50; i++) // ヘッダー部分だけに限ると、処理時間が半分になる。正規表現のマッチングが時間がかかっているみたいだ。 sb.Append(sr.ReadLine()); txt = sb.ToString(); //txt = sr.ReadToEnd(); } string mapName = ""; // 地点名は2012/5/22時点では未対応 int ID = 0; // ID番号 Pos size = new Pos(); // マップサイズ(縦方向と横方向のメッシュ数) Blh upperRight = new Blh(), lowerLeft = new Blh(); Match m; m = this.meshID.Match(txt); if (m.Success) ID = int.Parse(m.Groups["ID"].Value); m = this.lowerCorner.Match(txt); if (m.Success) upperRight = new Blh(double.Parse(m.Groups["lat"].Value), double.Parse(m.Groups["lon"].Value)); m = this.upperCorner.Match(txt); if (m.Success) lowerLeft = new Blh(double.Parse(m.Groups["lat"].Value), double.Parse(m.Groups["lon"].Value)); m = this.size.Match(txt); if (m.Success) size = new Pos(int.Parse(m.Groups["width"].Value) + 1, int.Parse(m.Groups["hight"].Value) + 1); RectangleField corner = new RectangleField(upperRight, lowerLeft); return new Info(mapName, ID, size, corner, Model.JPGIS2x_GML); }
/// <summary> /// DEMクラスの読み込みテストおよびマネジメント機能のテスト、さらにマップの結合テストを実施します /// <para>指定した座標で張る長方形領域を含む地図を生成します。</para> /// <para>座標を省略すると、Blh(33.0432, 130.559), Blh(32.6159, 130.9659)(熊本県付近)が使用されます。</para> /// <para>Taskクラスを利用して処理がすぐに戻るようにしておりますが、実際には若干の間をおいてファイルが生成されることにご注意ください。</para> /// </summary> /// <param name="x1">座標1</param> /// <param name="x2">座標2</param> public static void SaveBigMapAsBmp(Blh x1 = new Blh(), Blh x2 = new Blh()) { DEM dem = new DEM(); try { dem.AddDirWithDialog(); if (dem.Count != 0) { MapDem map = null; var tsk = Task.Factory.StartNew(() => { if (x1 == new Blh() && x1 == x2) map = dem.CreateMap(new RectangleField(new Blh(33.0432, 130.559), new Blh(32.6159, 130.9659))); else map = dem.CreateMap(new RectangleField(x1, x2)); //System.Diagnostics.Stopwatch sw2 = System.Diagnostics.Stopwatch.StartNew(); if (map != null) map.SaveAsImage("ToBmpTest.bmp", 0.0f); //sw2.Stop(); // ストップウォッチを止める //Console.WriteLine("マップ画像出力にかけた処理時間: " + sw2.Elapsed); // 結果を表示する }); } } catch (SystemException e) { Console.WriteLine(e.Message); } return; }
/**************************** メンバ関数(メソッド) ************************/ /// <summary> /// 指定座標のデータを返すことが可能かどうかを返す /// </summary> /// <param name="pos">指定座標(lat,lon)</param> /// <returns>true: 使用可能</returns> public Boolean CheckAvailability(Blh pos) { Boolean ans = false; foreach (Manager man in this.manager) // 要素がなければ実行されない { if (man.CheckAvailability(pos)) { ans = true; break; } } return ans; }
/// <summary> /// 渡されたマップを用いて初期化する /// <para>呼び出すのは一回きりにしてください</para> /// </summary> /// <param name="info">マップの情報</param> private void Init(Info info) { if (this.dict.Count == 0) { this.mapSize = info.Field.Size; this.amountOfMeshInMap = info.Size; this.meshSize = new Blh(this.mapSize.B / (double)this.amountOfMeshInMap.y, this.mapSize.L / (double)this.amountOfMeshInMap.x); this.offset = this.GetMapOffset(info); } return; }
/// <summary> /// 2つのBlh座標について、小数点以下digit桁の精度で一致しているかどうか検査する /// <para>8桁としていて一致しているとみなされた場合、最大でも1.2 mm程度の誤差しか有りません。</para> /// </summary> /// <param name="x1">検査したい座標1</param> /// <param name="x2">検査したい座標2</param> /// <param name="digit">桁数</param> /// <returns>true: 緯度経度は一致しているとみなせる</returns> private Boolean ComparePosition(Blh x1, Blh x2, int digit) { long xb1 = (long)(Math.Round(x1.B * Math.Pow(10.0, (double)digit))); long xb2 = (long)(Math.Round(x2.B * Math.Pow(10.0, (double)digit))); long xl1 = (long)(Math.Round(x1.L * Math.Pow(10.0, (double)digit))); long xl2 = (long)(Math.Round(x2.L * Math.Pow(10.0, (double)digit))); return (xb1 == xb2 && xl1 == xl2); }
/// <summary> /// 距離演算その4 /// <para> /// 2点をXYZ座標へ変換して、そのベクトルの差分のノルムを取る(== 直線距離[m])という処理を行います。 /// 40 kmでその2に対して0.1 m以下の差を生じる。 /// 地表における距離10 km以下であれば実用上は問題はないと考えられる。 /// 演算の速さが取り柄です。 /// 標高も演算に使われます。 /// また、標高が高い(高度数十km)地点での短距離はこのメソッドの方がその2や3よりも精度上優位でしょう。 /// </para> /// </summary> /// <param name="pos">求めたい地点の座標</param> /// <returns>距離</returns> public double GetDistance4(Blh pos) { Ecef me = this.ToXYZ(); Ecef he = pos.ToXYZ(); return (me - he).Norm; }
/// <summary> /// 距離演算その3 /// <para>ヒュベニの公式を利用しているGetDistance()を用いて、楕円体面上の距離[m]を求めます。</para> /// <para>精度は実距離100 km当たり、1 m程度です。</para> /// <para>なお、高度は無視して楕円体面上での距離を求めています。</para> /// </summary> /// <param name="pos">求めたい地点の座標</param> /// <returns>距離</returns> public double GetDistance3(Blh pos) { Length distance0 = this.GetDistance(pos); double distance3 = Math.Sqrt(distance0.E * distance0.E + distance0.N * distance0.N); return distance3; }
/// <summary> /// 距離演算その2 /// <para> /// <para>引数で指定された座標までの距離[m]を返します。</para> /// 参考:http://homepage3.nifty.com/kubota01/distance.htm /// 距離が50kmを超えるようなら、こちらのメソッドの使用を推奨します。 /// ただし、数百km以上であればその5またはその6の使用を推奨します。 /// <para>なお、高度は無視して楕円体面上での距離を求めています。</para> /// </para> /// </summary> /// <param name="pos">求めたい地点の座標</param> /// <returns>距離</returns> public double GetDistance2(Blh pos) { Ecef me = this.ToXYZ(); Ecef he = pos.ToXYZ(); GlobalDatum _datum = new GlobalDatum(this.DatumKind); double d_straight = Math.Sqrt(Math.Pow(me.x - he.x, 2.0) + Math.Pow(me.y - he.y, 2.0) + Math.Pow(me.z - he.z, 2.0)); // XYZ直交座標系を用いた直線距離(線は地中に潜る) double Nme = _datum.a / Math.Sqrt(1.0d - _datum.e2 * Math.Sin(this.ToRadian().B)); double Nse = _datum.a / Math.Sqrt(1.0d - _datum.e2 * Math.Sin(pos.ToRadian().B)); double N = (Nse + Nme) / 2.0d; // 平均の半径のようなもの double angle = Math.Asin(d_straight / 2.0d / N); // 半射程角(絵を描けば分かる) return 2.0d * angle * N; }
/// <summary> /// 距離演算その1 /// <para>ヒュベニの公式を利用して、引数で指定された座標までの距離[m]を返します。</para> /// <para>GetDistance2()と比較して、40 km差で0.01 m以下の差が生じます。</para> /// <para>最短距離を求めているわけではないことに注意してください。</para> /// <para>なお、高度は無視して楕円体面上での距離を求めています。</para> /// </summary> /// <param name="pos">求めたい地点の座標</param> /// <returns>南北・東西方向の距離[m]を構造体で返す</returns> public Length GetDistance(Blh pos) { Length ans; Blh me = this.ToDegree(); // 自身のコピー Blh he = pos.ToDegree(); // 引数のコピー。単位はdegに統一する Field.RectangleField field = new Field.RectangleField(me, he); Blh center = field.Center; ans = new Length(field.DifferInLon * center.GetUnitLengthForEN().E, field.DifferInLat * center.GetUnitLengthForEN().N); // 緯度・経度差から距離を求める。 return ans; }
/******************** メソッド ****************************/ /// <summary> /// XYZからBlh座標系(緯度・経度・楕円体高[m])へ変換する /// </summary> /// <param name="datum">変換後の測地系</param> /// <returns>Blhに変換した結果</returns> public Blh ToBLH(Datum datum = Datum.WGS84) { Blh ans = new Blh(); double a, b, e, n, h, p, t, sint, cost; GlobalDatum _datum = new GlobalDatum(datum); ans.H = -_datum.a; if (this.x == 0 && this.y == 0 && this.z == 0) return ans; a = _datum.a; // 長半径 b = _datum.b; // 短半径 e = Math.Sqrt(_datum.e2); // 離心率 // 座標変換のためのパラメータ h = Math.Pow(a, 2) - Math.Pow(b, 2); p = Math.Sqrt(Math.Pow(this.x, 2) + Math.Pow(this.y, 2)); t = Math.Atan2(this.z * a, p * b); sint = Math.Sin(t); cost = Math.Cos(t); ans.B = Math.Atan2(this.z + h / b * Math.Pow(sint, 3), p - h / a * Math.Pow(cost, 3)); // 緯度[rad]を計算する ans.L = Math.Atan2(this.y, this.x); // 経度[rad]を求める n = a / Math.Sqrt(1 - _datum.e2 * Math.Pow(Math.Sin(ans.B), 2)); // 卯酉線曲率半径 ans.H = (p / Math.Cos(ans.B)) - n; // 楕円体高[m] return ans; }
/// <summary> /// 指定された緯度経度に沿った標高を配列で返す /// </summary> /// <param name="pos">座標の格納された配列</param> /// <returns>指定された座標のマップ情報を返す</returns> public float[] GetHeight(Blh[] pos) { float[] ans = new float[pos.Length]; Parallel.For(0, pos.Length, i => { ans[i] = this.GetHeight(pos[i]); }); return ans; }
/// <summary> /// 指定された緯度経度の標高を返す /// <para>保持しているDEMモデルの内、分解能が最も細かいモデルの結果を返します。</para> /// <para>該当なしの場合は、float.NaNを返します。</para> /// </summary> /// <param name="pos">指定座標</param> /// <returns>指定された座標のマップ情報を返す</returns> public float GetHeight(Blh pos) { this.manager.Sort(); // リストを小さい順に並び替える float ans = float.NaN; for (int i = 0; i < this.manager.Count; i++ ) { ans = this.manager[i].GetHeight(pos); if (ans != float.NaN) break; } return ans; }
/// <summary> /// 指定座標の値を返すことができるか返す /// </summary> /// <param name="pos">指定座標</param> /// <returns>true: 値を返すことは可能</returns> public Boolean CheckAvailability(Blh pos) { Pos address = this.GetMapAddress(pos); if (this.dict.ContainsKey(address) == true) return true; else return false; }
/// <summary> /// 指定された緯度経度の標高を返す /// <para>該当なしの場合は、float.NaNを返します</para> /// </summary> /// <param name="pos">指定座標</param> /// <returns>指定された座標のマップ情報を返す</returns> public float GetHeight(Blh pos) { Pos address = this.GetMapAddress(pos); if (this.dict.ContainsKey(address) == true) { if (this.dict[address].IsLoaded == false) this.dict[address].Load(); // まだデータを読み込んでいなければ読み込みを実施します。 return this.dict[address].DemMap.GetValue(pos); } else return float.NaN; }
/// <summary> /// 距離演算その5 /// <para>Lambert-Andoyerの式を用いて引数で指定された座標までの距離[m]を返します。</para> /// <para>10 m程度の距離で0.2 mm程度の誤差を生じます。数百km以上の長距離になると誤差は少なめです。</para> /// <para>距離演算その6と比較して、10 m程のごく短距離において、0.01 pm(ピコメートル)の差が生じました。また、通常距離では差は生じませんでした。</para> /// <para>参考文献</para> /// <para>[1] 河合,測地線航海算法,富山高専 航海科学研究室,http://www.toyama-cmt.ac.jp/~mkawai/lecture/sailing/geodetic/geosail.html#note1 ,2012/6.</para> /// </summary> /// <param name="pos">求めたい地点の座標</param> /// <returns>測地線長<para>未知の測地系の場合、非値を返します。</para></returns> public double GetDistance5(Blh pos) { Blh me = this.ToRadian(); Blh he = pos.ToRadian(); GlobalDatum _datum = new GlobalDatum(this.DatumKind); double myParametricLat = Math.Atan(_datum.b / _datum.a * Math.Tan(me.B)); double hisParametricLat = Math.Atan(_datum.b / _datum.a * Math.Tan(he.B)); double sphericalDistance = Math.Acos(Math.Sin(myParametricLat) * Math.Sin(hisParametricLat) + Math.Cos(myParametricLat) * Math.Cos(hisParametricLat) * Math.Cos(me.L - he.L)); double C = Math.Pow(Math.Sin(myParametricLat) + Math.Sin(hisParametricLat), 2.0); double D = Math.Pow(Math.Sin(myParametricLat) - Math.Sin(hisParametricLat), 2.0); double correction = _datum.f / 8.0 * ( (Math.Sin(sphericalDistance) - sphericalDistance) * C / Math.Pow(Math.Cos(sphericalDistance / 2.0), 2.0) - (Math.Sin(sphericalDistance) + sphericalDistance) * D / Math.Pow(Math.Sin(sphericalDistance / 2.0), 2.0) ); double geodeticDistance = double.NaN; geodeticDistance = _datum.a * (sphericalDistance + correction); return geodeticDistance; }
/// <summary> /// 指定座標が含まれるマップのアドレスを返す /// </summary> /// <param name="pos">検査したい座標</param> /// <returns>マップのアドレス</returns> private Pos GetMapAddress(Blh pos) { pos.Unit = AngleUnit.Degree; // 単位を度に統一する pos.DatumKind = Datum.WGS84; // 測地系も統一 pos -= offset; // マップ原点を引いて、オフセットを取る int x = (int)(pos.L / this.mapSize.L); if (pos.L < 0.0) x -= 1; int y = (int)(pos.B / this.mapSize.B); if (pos.B < 0.0) y -= 1; return new Pos(x, y); }
/// <summary> /// 距離演算その6 /// <para>Lambert-Andoyerの式を変形した小野の公式を用い引数で指定された座標までの距離[m]を返します。</para> /// <para>参考文献</para> /// <para>[1] 河合,測地線航海算法,富山高専 航海科学研究室,http://www.toyama-cmt.ac.jp/~mkawai/lecture/sailing/geodetic/geosail.html#note1 ,2012/6.</para> /// <para></para> /// </summary> /// <param name="pos">求めたい地点の座標</param> /// <returns>測地線長<para>未知の測地系の場合、非値を返します。</para></returns> public double GetDistance6(Blh pos) { Blh me = this.ToRadian(); Blh he = pos.ToRadian(); GlobalDatum _datum = new GlobalDatum(this.DatumKind); double myParametricLat = Math.Atan(_datum.b / _datum.a * Math.Tan(me.B)); double hisParametricLat = Math.Atan(_datum.b / _datum.a * Math.Tan(he.B)); double sphericalDistance = Math.Acos(Math.Sin(myParametricLat) * Math.Sin(hisParametricLat) + Math.Cos(myParametricLat) * Math.Cos(hisParametricLat) * Math.Cos(me.L - he.L)); double C = Math.Pow(Math.Sin(myParametricLat) + Math.Sin(hisParametricLat), 2.0); double D = Math.Pow(Math.Sin(myParametricLat) - Math.Sin(hisParametricLat), 2.0); double P = (_datum.a - _datum.b) * (sphericalDistance - Math.Sin(sphericalDistance)) / (4 * (1 + Math.Cos(sphericalDistance))); double Q = (_datum.a - _datum.b) * (sphericalDistance + Math.Sin(sphericalDistance)) / (4 * (1 - Math.Cos(sphericalDistance))); double geodeticDistance = double.NaN; geodeticDistance = _datum.a * sphericalDistance - C * P - D * Q; return geodeticDistance; }
/// <summary> /// コンストラクタ /// </summary> public Manager() { this.dict = new System.Collections.Generic.Dictionary<Pos, DemSet>(); this.meshSize = new Blh(); }
/// <summary> /// 2地点間の中間座標を返す /// <para>(現時点では)メルカトル図法上の中間点を算出します。従って極点をまたがるような2点間の中点を求める用途には向いていません。</para> /// <para>また、2地点間の中間地点は地球上に2点取り得ますが、2点間を結ぶ距離が短くなる方を採用しています。</para> /// </summary> /// <param name="pos">第2の座標</param> /// <returns> /// 本インスタンスと第2の座標間の中間座標 /// <para>新しく生成する座標の単位と測地系は本オブジェクトに合わせます。</para> /// </returns> public Blh GetMedian(Blh pos) { Blh me = this; // 自身のコピー Blh he = pos; // 引数のコピー he.Unit = this.Unit; // 単位は統一する he.DatumKind = this.DatumKind; // 測地系も統一する Blh relative = he - me; // me基準の相対座標を計算 double centerLon = me.L + relative.L / 2.0; // 2地点の中央の経度を計算(この時点ではAbs(180.0又はπ)を超えるかもしれない) return new Blh((me.B + he.B) / 2.0d, centerLon, (me.H + he.H) / 2.0d, this.Unit, this.DatumKind); // 2地点の中間座標を求める }