3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

天文潮位を計算する (Rust)

Last updated at Posted at 2025-01-24

参考: https://www1.kaiho.mlit.go.jp/kenkyu/report/rhr60/rhr60_r_01.pdf 潮汐表の計算について 海洋情報部研究報告 第60号 令和4年3月18日

↑の計算を Rust で実装してみました。資料だけみても読み解きにくい部分があるので、同様のことをされる方のために、Gistのかわりにここにコードを貼っておきます。以上です。

//! 参考: https://www1.kaiho.mlit.go.jp/kenkyu/report/rhr60/rhr60_r_01.pdf
//! 潮汐表の計算について 海洋情報部研究報告 第60号 令和4年3月18日

use chrono::{Datelike, NaiveDate};

/// 各分潮の角速度 (度/時)
const OMEGAS: [f64; 60] = [
    0.0410686, 0.0821373, 0.5443747, 1.0158958, 1.0980331, 12.8542862, 12.9271398, 13.3986609,
    13.4715145, 13.9430356, 14.0251729, 14.4920521, 14.5695476, 14.9178647, 14.9589314, 15.0000000,
    15.0410686, 15.0821353, 15.1232059, 15.5125897, 15.5854433, 16.0569644, 16.1391017, 27.3416964,
    27.4238337, 27.8953548, 27.9682084, 28.4397295, 28.5125831, 28.9019669, 28.9841042, 29.0662415,
    29.4556253, 29.5284789, 29.9589333, 30.0000000, 30.0410667, 30.0821373, 30.5443747, 30.6265120,
    31.0158958, 42.9271398, 43.4761563, 43.9430356, 44.0251729, 45.0410686, 57.4238337, 57.9682084,
    58.4397295, 58.9841042, 59.0662415, 60.0000000, 60.0821373, 86.4079380, 86.9523127, 87.4238337,
    87.9682084, 88.0503457, 88.9841042, 89.0662415,
];

// NAGOYA
// https://www.data.jma.go.jp/kaiyou/db/tide/suisan/harms60.php?stn=NG&year=2025&tyear=2025
const PARAMS: [[f64; 2]; 60] = [
    [14.73, 145.84],
    [1.47, 26.92],
    [0.76, 81.97],
    [0.37, 0.42],
    [0.91, 115.08],
    [0.48, 147.04],
    [0.49, 156.30],
    [3.64, 156.98],
    [0.65, 161.63],
    [17.78, 166.66],
    [0.36, 205.04],
    [0.88, 178.56],
    [0.23, 174.23],
    [0.45, 178.50],
    [7.62, 182.74],
    [0.87, 32.96],
    [24.03, 186.49],
    [0.07, 285.27],
    [0.31, 167.51],
    [0.28, 206.52],
    [1.25, 208.62],
    [0.35, 307.38],
    [0.79, 239.49],
    [0.10, 52.03],
    [0.52, 192.88],
    [1.50, 172.97],
    [1.95, 197.10],
    [11.08, 174.52],
    [2.17, 173.21],
    [0.16, 150.21],
    [64.74, 177.70],
    [0.36, 277.40],
    [0.74, 166.05],
    [2.24, 181.17],
    [1.97, 204.37],
    [30.35, 203.85],
    [0.04, 69.45],
    [8.51, 199.23],
    [0.14, 349.01],
    [0.55, 25.78],
    [0.28, 10.85],
    [0.67, 282.17],
    [1.38, 197.55],
    [0.77, 298.62],
    [0.88, 283.96],
    [0.76, 38.92],
    [0.12, 19.60],
    [0.05, 339.82],
    [0.03, 345.27],
    [0.10, 90.60],
    [0.05, 57.23],
    [0.35, 305.41],
    [0.09, 252.79],
    [0.08, 273.53],
    [0.17, 270.95],
    [0.06, 299.24],
    [0.25, 300.12],
    [0.06, 255.82],
    [0.10, 331.95],
    [0.07, 336.29],
];

/// ユリウス通日を求める
fn julian_day(date: NaiveDate) -> f32 {
    let month = date.month();
    let a = (14 - month) / 12;
    let y = (date.year() as u32) + 4800 - a;
    let m = month + 12 * a - 3;
    let jd = date.day() + (153 * m + 2) / 5 + y * 365 + y / 4 - y / 100 + y / 400 - 32045;
    jd as f32 - 0.5
}

pub struct TideEstimator {
    v0: [f64; 60],
    f: [f64; 60],
    u: [f64; 60],
}

impl TideEstimator {
    pub fn new(date_begin: NaiveDate, date_mid: NaiveDate) -> Self {
        Self::setup_coefficients(date_begin, date_mid)
    }

    /// 天文潮位を推算する
    pub fn estimate(&self, lng: f64, z0: f64, params: &[[f64; 2]; 60], t: f64) -> f64 {
        z0 + (0..60)
            .map(|i| {
                let [hi, ki] = params[i];
                let a0 = match i {
                    0..=4 => 0,
                    5..=22 => 1,
                    23..=40 => 2,
                    41..=45 => 3,
                    46..=52 => 4,
                    _ => 6,
                } as f64;
                self.f[i]
                    * hi
                    * (a0 * lng + OMEGAS[i] * (t - 9.) + self.v0[i] + self.u[i] - ki)
                        .to_radians()
                        .cos()
            })
            .sum::<f64>()
    }

    /// 天文潮位を推算するための係数を準備する
    fn setup_coefficients(date_begin: NaiveDate, date_mid: NaiveDate) -> TideEstimator {
        let t_md = (julian_day(date_begin) as f64 - 2378496.) / 36525.;
        let t_sd = (julian_day(date_begin) as f64 - 2415021.) / 36525.;
        let t_mm = (julian_day(date_mid) as f64 - 2378496.) / 36525.;

        let s_d = 335.723436
            + 481267.887361 * t_md
            + 3.38888e-3 * t_md.powi(2)
            + 1.83333e-6 * t_md.powi(3);
        let p_d =
            225.397325 + 4069.053805 * t_md - 1.02869e-2 * t_md.powi(2) - 1.22222e-5 * t_md.powi(3);
        let h_d = 280.6824 + 36000.769325 * t_sd + 7.2222e-4 * t_sd.powi(2);
        let n_m =
            33.272936 - 1934.144694 * t_mm + 2.08028e-3 * t_md.powi(2) + 2.08333e-6 * t_mm.powi(3);
        let p_m =
            225.397325 + 4069.053805 * t_mm - 1.02869e-2 * t_mm.powi(2) - 1.22222e-5 * t_mm.powi(3);

        let cos_nm = n_m.to_radians().cos();
        let cos_2nm = (2. * n_m).to_radians().cos();
        let cos_3nm = (3. * n_m).to_radians().cos();
        let sin_nm = n_m.to_radians().sin();
        let sin_2nm = (2. * n_m).to_radians().sin();
        let sin_3nm = (3. * n_m).to_radians().sin();

        let f_mm = 1.0000 + (-0.1300 * cos_nm) + (0.0013 * cos_2nm) + (0.0000 * cos_3nm);
        let u_mm = (0.00 * sin_nm) + (0.00 * sin_2nm) + (0.00 * sin_3nm);

        let f_mf = 1.0429 + (0.4135 * cos_nm) + (-0.0040 * cos_2nm) + (0.0000 * cos_3nm);
        let u_mf = (-23.74 * sin_nm) + (2.68 * sin_2nm) + (-0.38 * sin_3nm);

        let f_o1 = 1.0089 + (0.1871 * cos_nm) + (-0.0147 * cos_2nm) + (0.0014 * cos_3nm);
        let u_o1 = (10.80 * sin_nm) + (-1.34 * sin_2nm) + (0.19 * sin_3nm);

        let f_k1 = 1.0060 + (0.1150 * cos_nm) + (-0.0088 * cos_2nm) + (0.0006 * cos_3nm);
        let u_k1 = (-8.86 * sin_nm) + (0.68 * sin_2nm) + (-0.07 * sin_3nm);

        let f_j1 = 1.0129 + (0.1676 * cos_nm) + (-0.0170 * cos_2nm) + (0.0016 * cos_3nm);
        let u_j1 = (-12.94 * sin_nm) + (1.34 * sin_2nm) + (-0.19 * sin_3nm);

        let f_oo1 = 1.1027 + (0.6504 * cos_nm) + (0.0317 * cos_2nm) + (-0.0014 * cos_3nm);
        let u_oo1 = (-36.68 * sin_nm) + (4.02 * sin_2nm) + (-0.57 * sin_3nm);

        let f_m2 = 1.0004 + (-0.0373 * cos_nm) + (0.0002 * cos_2nm) + (0.0000 * cos_3nm);
        let u_m2 = (-2.14 * sin_nm) + (0.00 * sin_2nm) + (0.00 * sin_3nm);

        let f_k2 = 1.0241 + (0.2863 * cos_nm) + (0.0083 * cos_2nm) + (-0.0015 * cos_3nm);
        let u_k2 = (-17.74 * sin_nm) + (0.68 * sin_2nm) + (-0.04 * sin_3nm);

        let (f_l2, u_l2) = {
            let x = 1.
                - 0.2505 * (2. * p_m).to_radians().cos()
                - 0.1102 * (2. * p_m - n_m).to_radians().cos()
                - 0.0156 * (2. * p_m - 2. * n_m).to_radians().cos()
                - 0.0370 * (n_m).to_radians().cos();
            let y = -0.2505 * (2. * p_m).to_radians().sin()
                - 0.1102 * (2. * p_m - n_m).to_radians().sin()
                - 0.0156 * (2. * p_m - 2. * n_m).to_radians().sin()
                - 0.0370 * (n_m).to_radians().sin();
            ((x * x + y * y).sqrt(), f64::atan2(y, x).to_degrees())
        };
        let (f_m1, u_m1) = {
            let x = 2. * p_m.to_radians().cos() + 0.4 * (p_m - n_m).to_radians().cos();
            let y = p_m.to_radians().sin() + 0.2 * (p_m - n_m).to_radians().sin();
            ((x * x + y * y).sqrt(), f64::atan2(y, x).to_degrees())
        };

        let calc_v0 = |a1: i32, a2: i32, a3: i32, a4: i32| {
            (a1 as f64 * s_d) + (a2 as f64 * h_d) + (a3 as f64 * p_d) + a4 as f64
        };

        let mut v0 = [0.0; 60];
        let mut f = [1.0; 60];
        let mut u = [0.0; 60];

        // Sa 太陽年周潮
        v0[0] = calc_v0(0, 1, 0, 0);

        // Ssa 太陽半年周潮
        v0[1] = calc_v0(0, 2, 0, 0);

        // Mm 太陰月周潮
        v0[2] = calc_v0(1, 0, -1, 0);
        (f[2], u[2]) = (f_mm, u_mm);

        // MSf 日月合成半月周潮
        v0[3] = calc_v0(2, -2, 0, 0);
        (f[3], u[3]) = (f_m2, -u_m2);

        // Mf 太陰半月周潮
        v0[4] = calc_v0(2, 0, 0, 0);
        (f[4], u[4]) = (f_mf, u_mf);

        // 2Q1 二次太陰楕率潮
        v0[5] = calc_v0(-4, 1, 2, 270);
        (f[5], u[5]) = (f_o1, u_o1);

        // SIG1
        v0[6] = calc_v0(-4, 3, 0, 270);
        (f[6], u[6]) = (f_o1, u_o1);

        // Q1 主太陰楕率潮
        v0[7] = calc_v0(-3, 1, 1, 270);
        (f[7], u[7]) = (f_o1, u_o1);

        // RHO1 主太陰出差潮
        v0[8] = calc_v0(-3, 3, -1, 270);
        (f[8], u[8]) = (f_o1, u_o1);

        // O1 主太陰日周潮
        v0[9] = calc_v0(-2, 1, 0, 270);
        (f[9], u[9]) = (f_o1, u_o1);

        // MP1
        v0[10] = calc_v0(-2, 3, 0, 90);
        (f[10], u[10]) = (f_m2, u_m2);

        // M1 副太陰楕率潮
        v0[11] = calc_v0(-1, 1, 0, 90);
        (f[11], u[11]) = (f_m1, u_m1);

        // CHI1
        v0[12] = calc_v0(-1, 3, -1, 90);
        (f[12], u[12]) = (f_j1, u_j1);

        // PI1 主太陽楕率潮
        v0[13] = calc_v0(0, -2, 0, 193);

        // P1 主太陽日周潮
        v0[14] = calc_v0(0, -1, 0, 270);

        // S1 気象日周潮
        v0[15] = calc_v0(0, 0, 0, 180);

        // K1 日月合成日周潮
        v0[16] = calc_v0(0, 1, 0, 90);
        (f[16], u[16]) = (f_k1, u_k1);

        // PSI1 副太陽楕率潮
        v0[17] = calc_v0(0, 2, 0, 167);

        // PHI1 二次太陽日周潮
        v0[18] = calc_v0(0, 3, 0, 90);

        // THE1
        v0[19] = calc_v0(1, -1, 1, 90);
        (f[19], u[19]) = (f_j1, u_j1);

        // J1 小太陰楕率潮
        v0[20] = calc_v0(1, 1, -1, 90);
        (f[20], u[20]) = (f_j1, u_j1);

        // SO1
        v0[21] = calc_v0(2, -1, 0, 90);
        (f[21], u[21]) = (f_o1, -u_o1);

        // OO1 二次太陰日周潮
        v0[22] = calc_v0(2, 1, 0, 90);
        (f[22], u[22]) = (f_oo1, u_oo1);

        // OQ2
        v0[23] = calc_v0(-5, 2, 1, 180);
        (f[23], u[23]) = (f_o1 * f_o1, u_o1 * u_o1);

        // MNS2
        v0[24] = calc_v0(-5, 4, 1, 0);
        (f[24], u[24]) = (f_m2 * f_m2, 2. * u_m2);

        // 2N2 二次太陰楕率潮
        v0[25] = calc_v0(-4, 2, 2, 0);
        (f[25], u[25]) = (f_m2, u_m2);

        // MU2 太陰二均差潮
        v0[26] = calc_v0(-4, 4, 0, 0);
        (f[26], u[26]) = (f_m2, u_m2);

        // N2 主太陰楕率潮
        v0[27] = calc_v0(-3, 2, 1, 0);
        (f[27], u[27]) = (f_m2, u_m2);

        // NU2 主太陰出差潮
        v0[28] = calc_v0(-3, 4, -1, 0);
        (f[28], u[28]) = (f_m2, u_m2);

        // OP2
        v0[29] = calc_v0(-2, 0, 0, 180);
        (f[29], u[29]) = (f_o1, u_o1);

        // M2 主太陰半日周潮
        v0[30] = calc_v0(-2, 2, 0, 0);
        (f[30], u[30]) = (f_m2, u_m2);

        // MKS2
        v0[31] = calc_v0(-2, 4, 0, 0);
        (f[31], u[31]) = (f_m2 * f_k2, u_m2 + u_k2);

        // LAM2 副太陰出差潮
        v0[32] = calc_v0(-1, 0, 1, 180);
        (f[32], u[32]) = (f_m2, u_m2);

        // L2 副太陰楕率潮
        v0[33] = calc_v0(-1, 2, -1, 180);
        (f[33], u[33]) = (f_l2, u_l2);

        // T2 主太陽楕率潮
        v0[34] = calc_v0(0, -1, 0, 283);

        // S2 主太陽半日周潮
        v0[35] = calc_v0(0, 0, 0, 0);

        // R2 副太陽楕率潮
        v0[36] = calc_v0(0, 1, 0, 257);

        // K2 日月合成半日周潮
        v0[37] = calc_v0(0, 2, 0, 0);
        (f[37], u[37]) = (f_k2, u_k2);

        // MSN2
        v0[38] = calc_v0(1, 0, -1, 0);
        (f[38], u[38]) = (f_m2 * f_m2, 2. * u_m2);

        // KJ2
        v0[39] = calc_v0(1, 2, -1, 180);
        (f[39], u[39]) = (f_k1 * f_j1, u_k1 + u_j1);

        // 2SM2
        v0[40] = calc_v0(2, -2, 0, 0);
        (f[40], u[40]) = (f_m2, -u_m2);

        // MO3
        v0[41] = calc_v0(-4, 3, 0, 270);
        (f[41], u[41]) = (f_m2 * f_o1, u_m2 + u_o1);

        // M3
        v0[42] = calc_v0(-3, 3, 0, 180);
        (f[42], u[42]) = (f_m2.powf(1.5), 1.5 * u_m2);

        // SO3
        v0[43] = calc_v0(-2, 1, 0, 270);
        (f[43], u[43]) = (f_o1, u_o1);

        // MK3
        v0[44] = calc_v0(-2, 3, 0, 90);
        (f[44], u[44]) = (f_m2 * f_k1, u_m2 + u_k1);

        // SK3
        v0[45] = calc_v0(0, 1, 0, 90);
        (f[45], u[45]) = (f_k1, u_k1);

        // MN4
        v0[46] = calc_v0(-5, 4, 1, 0);
        (f[46], u[46]) = (f_m2 * f_m2, 2. * u_m2);

        // M4 太陰1/4日周潮
        v0[47] = calc_v0(-4, 4, 0, 0);
        (f[47], u[47]) = (f_m2 * f_m2, 2. * u_m2);

        // SN4
        v0[48] = calc_v0(-3, 2, 1, 0);
        (f[48], u[48]) = (f_m2, u_m2);

        // MS4
        v0[49] = calc_v0(-2, 2, 0, 0);
        (f[49], u[49]) = (f_m2, u_m2);

        // MK4
        v0[50] = calc_v0(-2, 4, 0, 0);
        (f[50], u[50]) = (f_m2 * f_k2, u_m2 + u_k2);

        // S4 太陽1/4日周潮
        v0[51] = calc_v0(0, 0, 0, 0);

        // SK4
        v0[52] = calc_v0(0, 2, 0, 0);
        (f[52], u[52]) = (f_k2, u_k2);

        // 2MN6
        v0[53] = calc_v0(-7, 6, 1, 0);
        (f[53], u[53]) = (f_m2.powi(3), 3. * u_m2);

        // M6 太陰1/6日周潮
        v0[54] = calc_v0(-6, 6, 0, 0);
        (f[54], u[54]) = (f_m2.powi(3), 3. * u_m2);

        // MSN6
        v0[55] = calc_v0(-5, 4, 1, 0);
        (f[55], u[55]) = (f_m2 * f_m2, 2. * u_m2);

        // 2MS6
        v0[56] = calc_v0(-4, 4, 0, 0);
        (f[56], u[56]) = (f_m2 * f_m2, 2. * u_m2);

        // 2MK6
        v0[57] = calc_v0(-4, 6, 0, 0);
        (f[57], u[57]) = (f_m2 * f_m2 * f_k2, 2. * u_m2 + u_k2);

        // 2SM6
        v0[58] = calc_v0(-2, 2, 0, 0);
        (f[58], u[58]) = (f_m2, u_m2);

        // MSK6
        v0[59] = calc_v0(-2, 4, 0, 0);
        (f[59], u[59]) = (f_m2 * f_k2, u_m2 + u_k2);

        Self { v0, f, u }
    }
}

fn main() {
    let begin = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
    let mid = NaiveDate::from_ymd_opt(2024, 7, 1).unwrap();
    let est = TideEstimator::new(begin, mid);

    let day = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
    let lng = 136. + 52. / 60. + 51. / 3600.;
    let z0 = 140.0;
    for hour in 0..24 {
        let h = est.estimate(
            lng,
            z0,
            &PARAMS,
            (day.signed_duration_since(begin).num_days() * 24 + hour) as f64,
        );
        println!("{hour}, {h}");
    }
}
3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?