/// <summary> /// コンストラクタ /// <para>中心座標と半径[km]を指定すると、その円を格納できる四角い領域を宣言します。</para> /// </summary> /// <param name="x">中心座標</param> /// <param name="radius">半径[km]</param> public RectangleField(Blh x, double radius) { this.param = new FieldParameter(x.Unit, x.DatumKind); var len = x.GetUnitLengthForEN(); var dn = radius * 1000.0 / len.N; var de = radius * 1000.0 / len.E; var pos = new Blh(dn, de, 0.0, AngleUnit.Degree, x.DatumKind); x.Unit = AngleUnit.Degree; RectangleField.SetLatAndEastWestLon(x - pos, ref this.param); RectangleField.SetLatAndEastWestLon(x + pos, ref this.param); }
/* プロパティ ************************************/ /* メソッド **************************************/ /// <summary> /// 文字列を解析して、そこに含まれる1エポック分の測位情報を返す /// </summary> /// <param name="line">被解析文字列</param> /// <returns>測位情報の解析データ<para>null: 解析不可能であった場合</para></returns> public PositioningInfo Parse(string line) { Match m = this.regexGroup.Match(line); // マッチチェック if (m.Success) { Blh pos = new Blh(); // 初期値は全て0になっている PositioningInfo ans = new PositioningInfo(); int day = int.Parse(m.Groups["day"].Value); int month = int.Parse(m.Groups["month"].Value); int year = int.Parse(m.Groups["year"].Value); int hour = int.Parse(m.Groups["hour"].Value); int min = int.Parse(m.Groups["min"].Value); int sec = int.Parse(m.Groups["sec"].Value); ans.Time = new DateTime(year, month, day, hour, min, sec); pos.B = double.Parse(m.Groups["lat"].Value); pos.L = double.Parse(m.Groups["lon"].Value); pos.H = double.Parse(m.Groups["height"].Value); ans.Position = pos; return ans; } else return null; }
/// <summary> /// コンストラクタ /// <para>2つの座標を指定すると、大小関係からマップの座標を自動的に決定する。</para> /// <para>経度に関しては、より小さな領域となる座標を選択する。</para> /// </summary> /// <param name="x1">座標1</param> /// <param name="x2">座標2</param> /// <param name="unit">単位(°or rad)</param> /// <param name="datum">測地系</param> public RectangleField(Blh x1, Blh x2, AngleUnit unit = AngleUnit.Degree, Datum datum = Datum.WGS84) { this.param = new FieldParameter(unit, datum); RectangleField.SetLatAndEastWestLon(x1, ref this.param); RectangleField.SetLatAndEastWestLon(x2, ref this.param); }
/// <summary> /// 緯度経度を更新する /// </summary> /// <param name="additionalPos">追加座標</param> /// <param name="param">領域パラメータ</param> private static void SetLatAndEastWestLon(Blh additionalPos, ref FieldParameter param) { additionalPos.Unit = param.unit; // 単位を変換(統一する) additionalPos.DatumKind = param.datum; // 測地系を統一 // まずは緯度の値をセット(これは単純で良い) if (double.IsNaN(param.upperLat)) param.upperLat = additionalPos.B; // 未セットならセットするし、保持データと比べて大きい/小さければ格納する。 if (param.upperLat < additionalPos.B) param.upperLat = additionalPos.B; if (double.IsNaN(param.lowerLat)) param.lowerLat = additionalPos.B; if (param.lowerLat > additionalPos.B) param.lowerLat = additionalPos.B; // 次に経度の処理 if (double.IsNaN(param.eastLon)) param.eastLon = additionalPos.L; // 未セットならセット if (double.IsNaN(param.westLon)) param.westLon = additionalPos.L; if (double.IsNaN(param.centerLon)) { param.centerLon = additionalPos.L; // 初回時は代入するだけ } else { // まずは経度の東端と西端の処理 Blh currentCenter = new Blh(0, param.centerLon, 0, param.unit, param.datum); // 中央の経度を持つ座標を生成(このメソッド中では、緯度は0で宣言して引き算しないと、Blh構造体の仕様による要因で計算を誤る。) Blh relativeNewPos = additionalPos - currentCenter; // currentCenter基準の相対座標を計算 if (relativeNewPos.L > 0.0) // additionalPosがcurrentCenterよりも東側にある { Blh currentEast = new Blh(0, param.eastLon, 0, param.unit, param.datum); // 東端の経度を持つ座標を生成 Blh relativeCurrentEast = currentEast - currentCenter; // currentCenter基準の現東端の相対座標を計算 if (relativeNewPos.L > relativeCurrentEast.L) param.eastLon = additionalPos.L; // より東に属する方を採用する } else if (relativeNewPos.L < 0.0) // additionalPosがcurrentCenterよりも西側にある { Blh currentWest = new Blh(0, param.westLon, 0, param.unit, param.datum); // 西端の経度を持つ座標を生成 Blh relativeCurrentWest = currentWest - currentCenter; // currentCenter基準の現西端の相対座標を計算 if (relativeNewPos.L < relativeCurrentWest.L) param.westLon = additionalPos.L; // より西に属する方を採用する } // 次に経度中央の計算(変数のスコープがうざいのでかっこを付けてスコープを限定化する) // 以下は新規追加の座標が中央座標に対してπ以上の更新量が無ければ成り立つ。上のアルゴリズムを変更する際には細心の注意を払うこと。 { Blh currentWest = new Blh(0, param.westLon, 0, param.unit, param.datum); // 西端の経度を持つ座標を生成 Blh currentEast = new Blh(0, param.eastLon, 0, param.unit, param.datum); // 東端の経度を持つ座標を生成 Blh candidate = currentWest.GetMedian(currentEast); // 中間の座標を計算する。この時点では領域のセンターかどうかは不明。 Blh relationCandidate = candidate - currentEast; // 東端を基準に見た候補座標の相対座標を計算 Blh relatinCurrentCenter = currentCenter - currentEast; // 東端を基準に見た現中央座標の相対座標を計算 double half = 180.0; if (param.unit == AngleUnit.Radian) half = half * Math.PI / 180.0; // 経度をπだけ進める準備 if (relationCandidate.L * relatinCurrentCenter.L < 0) candidate = new Blh(0, candidate.L + half, 0, param.unit, param.datum); // 地球の反対側でなかったかをチェックして、反対側ならπ進める param.centerLon = candidate.L; // 中心座標を更新 } } return; }
/// <summary> /// 領域をBlh型の配列として返す /// <para>格納の順番はKMLのポリゴンで四角く描かれる順番にしています。</para> /// </summary> /// <returns>領域の四隅をBlh型の配列に加工したもの</returns> public Blh[] ToArray() { Blh[] arr = new Blh[4]; arr[0] = this.UpperLeft; arr[1] = this.UpperRight; arr[2] = this.LowerRight; arr[3] = this.LowerLeft; return arr; }
/// <summary> /// 座標の配列から領域を生成する /// <para>自動的に過去の履歴と比較されて領域が拡張されます。</para> /// </summary> /// <param name="pos">セットする座標</param> public void Set(Blh[] pos) { foreach (Blh _pos in pos) this.Set(_pos); }
/// <summary> /// 座標をセットする /// <para>自動的に過去の履歴と比較されて領域が拡張されます。</para> /// </summary> /// <param name="pos">セットする座標</param> public void Set(Blh pos) { SetLatAndEastWestLon(pos, ref this.param); return; }
/// <summary> /// 領域を指定マージン[km]にて拡張した領域を返します /// <para>非破壊的メソッド</para> /// <para>経度方向は高緯度に合わせ、緯度方向は低緯度に合わせて拡張します。</para> /// </summary> /// <param name="marginKm">マージン[km]<para>負値の場合は処理を行いません。</para></param> /// <returns>拡張済みのオブジェクト<para>引数のmarginが負値の場合はnullを返します。</para></returns> public RectangleField Extend(double marginKm) { RectangleField ans = null; if (marginKm > 0) { ans = new RectangleField(this); Blh highLat, lowLat; if (Math.Abs(this.LowerLeft.B) > Math.Abs(this.UpperLeft.B)) { highLat = this.LowerLeft; lowLat = this.UpperLeft; } else { highLat = this.UpperLeft; lowLat = this.LowerLeft; } double n = marginKm * 1000.0 / lowLat.GetUnitLengthForEN().N; // 緯度が低い方が緯度方向の単位距離が短い double e = marginKm * 1000.0 / highLat.GetUnitLengthForEN().E; // 緯度が高い方が経度方向の単位距離は短い Blh pad = new Blh(n, e); ans.Set(this.UpperRight + pad); ans.Set(this.LowerLeft - pad); } return ans; }
/// <summary> /// GGAを解析して、測位情報を返す /// <para>現時点では緯度と経度と楕円体高しか返していないんだけど・・・</para> /// </summary> /// <param name="str">解析文字列</param> /// <returns></returns> public static PositioningInfo Parse(string str) { Blh pos = new Blh(); // 初期値は全て0になっている PositioningInfo ans = new PositioningInfo(); Match m = GGA.regexGroup.Match(str); // マッチチェック if (m.Success) { string clock = m.Groups["clock"].Value; string lat = m.Groups["lat"].Value; string lon = m.Groups["lon"].Value; string alt_MSL = m.Groups["Alt_MSL"].Value; string alt_Geoid = m.Groups["Alt_Geoid"].Value; // 緯度経度と高度を取得する double _lat = 0.0, _lon = 0.0; if (lat != "") _lat = double.Parse(lat); if (_lat != 0.0) _lat = (double)((int)(_lat / 100.0d)) + (_lat % 100.0) / 60.0d; // 単位を度へ変換する pos.B = _lat; if (m.Groups["NorS"].Value == "S") pos.B = -pos.B; // 南緯は負数とする if (lon != "") _lon = double.Parse(lon); if (_lon != 0.0) _lon = (double)((int)(_lon / 100.0d)) + (_lon % 100.0) / 60.0d; pos.L = _lon; if (m.Groups["EorW"].Value == "W") pos.L = -pos.L; // 西経は負数とする if (alt_MSL != "" && alt_Geoid != "") pos.H = double.Parse(alt_MSL) + double.Parse(alt_Geoid);// 平均海水面高とジオイド高を足して楕円体高とする } ans.Position = pos; return ans; }