LoginSignup
1
0

More than 3 years have passed since last update.

[Rust] 角度の表記: 弧度法, 度数法, 時間表記

Posted at

これは半年ほど前に書きかけて中断した下書きですが, クレートとしては永遠に完成しなさそうですし, これだけでも多少は有用な気がするので放流しておきます.

角度の表現

まず最初に角度の数値表現について確認しておきます. 良く知られている表現として度数法と弧度法がありますが, 天文学においては時間表記が現代でも残っています.

  • 弧度法: 半円がなす角を π = 3.14... とする単位です. 単位は radian.
    • 地球の赤道上の経度差 1.568E-4 rad が 1 メートルに対応します.
    • 京都御所は緯度 +0.61129 rad, 経度 2.369496 rad です.
  • 度数法: 半円がなす角を 180° とする単位です. 単位は度 (degree).
    • 小数以下を表すために分 (1' = 1°/60) と秒 (1'' = 1'/60) を用いることがあります.
    • 地球の赤道上の経度差 0°0'32.34'' が 1 メートルに対応します.
    • 京都御所は緯度 +35°1'26.''83, 経度 135°45'43.''67 です.
  • 時間表記: 半円がなす角を 12h とする単位です. 単位は時間 (hour).
    • 小数以下を表すために分 (1h = 60m) と秒 (1m = 60s) を用いることがあります.
    • 地球の赤道上の経度差 0h0m2.s16 が 1 メートルに対応します.
    • 京都御所は緯度 +2h20m5.s79, 経度 9h3m2.s91 です. このように経度を時間表記するとちょうど時差 (つまり地球が自転するのに要する時間) を与えるため, 経度に相当する量を表記するのに便利なのです (逆に緯度には用いません).

度数法と時間表記で分と秒の呼び方が同じですがその大きさは 15 倍違う (1h = 15°, 1m = 15' 1s = 15'') ので注意が必要です. この区別を明確にするため, 度数法は 分角 (minute of arc, moa) と 秒角 (second of arc, soa), 時間表記は 分 (minute, min) と 秒 (second, sec) と呼ぶことにします.

参考文献: 角度表示 - 天文学辞典 (日本天文学会)

Rust での扱い

角度を扱うプログラムを書くときには, 時間表記で扱っていたつもりなのに度数法の値だった, などのミスが頻繁に起こり得ます. そこで完全に別の型にしてしまえば, そのようなミスをコンパイル時に検出することができるでしょう. 弧度法は普通に f64 で良いので, 度数法と時間表記のために struct Degrees, Hours を用意します. おまけに初期化用に new メソッドもつけておきます.

#[derive(Debug, Clone, Copy)]
pub struct Degrees {
    deg: i16,
    moa: u8,
    soa: f32,
}

#[derive(Debug, Clone, Copy)]
pub struct Hours {
    hour: i8,
    min: u8,
    sec: f32,
}

impl Degrees {
    pub fn new(deg: i16, moa: u8, soa: f32) -> Self {
        Degrees { deg, moa, soa }
    }
}
impl Hours {
    pub fn new(hour: i8, min: u8, sec: f32) -> Self {
        Hours { hour, min, sec }
    }
}

なおフィールドアクセスしたければ各フィールドに pub が必要です.

文字列にフォーマットするためには std::fmt::Display トレイトを実装します.

use std::fmt;
impl fmt::Display for Degrees {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let decimals = f.precision().unwrap_or(3);
        write!(f, "{}°{}′{:.*}′′", self.deg, self.moa, decimals, self.soa)
    }
}
impl fmt::Display for Hours {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let decimals = f.precision().unwrap_or(3);
        write!(f, "{}h{}m{:.*}s", self.hour, self.min, decimals, self.sec)
    }
}

最後に, 弧度法, 度数法, 時間表記の間で自由に変換ができるように std::convert::{From, Into} を実装します.

use std::f64::consts::PI;

use std::convert::From;
impl From<f64> for Degrees {
    fn from(rad: f64) -> Degrees {
        let (sign, rad) = (rad.signum() as i16, rad.abs()%(2.*PI));
        let deg: f64 = rad*180./PI;
        let moa: f64 = deg.fract() * 60.;
        let soa: f64 = moa.fract() * 60.;

        Degrees::new(
            sign * deg.floor() as i16, moa.floor() as u8, soa as f32
        )
    }
}
impl From<f64> for Hours {
    fn from(rad: f64) -> Hours {
        let (sign, rad) = (rad.signum() as i8, rad.abs()%(2.*PI));
        let hour: f64 = rad*12./PI;
        let min: f64 = hour.fract() * 60.;
        let sec: f64 = min.fract() * 60.;

        Hours::new(
            sign * hour.floor() as i8, min.floor() as u8, sec as f32
        )
    }
}

use std::convert::Into;
impl Into<f64> for Degrees {
    fn into(self) -> f64 {
        ( self.deg as f64 + self.deg.signum() as f64 * ( self.moa as f64/60. + self.soa as f64/3600. ) ) * PI/180. 
    }
}
impl Into<f64> for Hours {
    fn into(self) -> f64 {
        ( self.hour as f64 + self.hour.signum() as f64 * ( self.min as f64/60. + self.sec as f64/3600. ) ) * PI/12. 
    }
}

impl From<Hours> for Degrees {
    fn from(hour: Hours) -> Degrees {
        let rad: f64 = hour.into();
        rad.into()
    }
}
impl From<Degrees> for Hours {
    fn from(deg: Degrees) -> Hours {
        let rad: f64 = deg.into();
        rad.into()
    }
}

これで自由に角度が扱えるようになりました. コード例として簡単なテストを載せておきます.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from_rad() {
        let rad = 2.369496;
        let deg: Degrees = rad.into();
        let hour: Hours = rad.into();
        println!("[FromRad] {:.3}={:.2}={:.2}", rad, deg, hour)
    }

    #[test]
    fn from_deg() {
        let deg = Degrees::new( 135, 45, 43.67 );
        let rad: f64 = deg.into();
        let hour: Hours = deg.into();
        println!("[FromDeg] {:.2}={}={:.2}", deg, rad, hour);
    }

    #[test]
    fn from_hour() {
        let hour = Hours::new( 9, 3, 2.91 );
        let rad: f64 = hour.into();
        let deg: Degrees = hour.into();
        println!("[FromHour] {:.2}={}={:.2}", hour, rad, deg);
    }
}

あとは FromStr の実装と, 変換を明示的に .to_hours() みたいな形で呼べるようにしたら便利になると思います.

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0