[C#]平面直角座標系と緯度経度を変換する

国土地理院の「測量計算サイト」に掲載されている計算式をC#でコーディングしただけですが…
他を探しても無かったので、自分用のバックアップを兼ねここにアップしておきます。
下のコードを拡張子「.cs」でコピペ&保存して、VisualStudioでプロジェクトに追加して、
Coordinate.cXY.LonLat2XY(dLon, dLat, 35.833333d, 141d, out dX, out dY)
みたいにすれば使うことが出来るかと思います。

測量分野1)平成十四年国土交通省告示第九号ではXが南北、Yが東西ですが、私自身が何度も間違えるので、Xを東西・Yを南北にしています。使うときはお気を付けください。
なお、UTM座標系ではXがEasting、YがNorthing2)”The y value, called the northing” Handbook for Transformation of
Datums…
p.44
になっています。(Twitterで@mapignさんに教えて頂きました、ありがとうございます)。

また、ミスなどがありましたら、教えてくれると喜びます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Coordinate
{
    /// <summary>
    /// 平面直角座標系と緯度経度を相互変換する
    /// 国土地理院の「測量計算サイト」の式をC#でコーディングした
    /// http://vldb.gsi.go.jp/sokuchi/surveycalc/main.html
    /// </summary>
    public static class cXY
    {
        const double daa = 6378137; //長半径
        const double dF = 298.257222101d; //逆扁平率
        const double dM0 = 0.9999; //平面直角座標系のY軸上における縮尺係数(UTM座標系の場合→0.9996)

        /// <summary>
        /// 緯度経度から任意原点の平面直角座標を求める
        /// </summary>
        /// <param name="Lon">経度[度]</param>
        /// <param name="Lat">緯度[度]</param>
        /// <param name="Lon0">原点:経度[度]</param>
        /// <param name="Lat0">原点:緯度[度]</param>
        /// <param name="X">X座標(東西方向、m)</param>
        /// <param name="Y">Y座標(南北方向、m)</param>
        public static void LonLat2XY(double Lon, double Lat, double Lon0, double Lat0, out double X, out double Y)
        {
            double dn = 1d / (2 * dF - 1);

            //ラジアン単位に
            Lon = Deg2Rad(Lon); Lat = Deg2Rad(Lat); Lon0 = Deg2Rad(Lon0); Lat0 = Deg2Rad(Lat0);

            double dt = Math.Sinh(atanh(Math.Sin(Lat)) - (2 * Math.Sqrt(dn)) / (1 + dn) * atanh(2 * Math.Sqrt(dn) / (1 + dn) * Math.Sin(Lat)));
            double dtb = Math.Sqrt(1 + Math.Pow(dt, 2));
            double dLmc = Math.Cos(Lon - Lon0);
            double dLms = Math.Sin(Lon - Lon0);
            double dXi = Math.Atan(dt / dLmc);
            double dEt = atanh(dLms / dtb);

            //α1→0~α5→4
            double[] dal = new double[6];
            dal[0] = 0;
            dal[1] = 1d / 2d * dn - 2d / 3d * Math.Pow(dn, 2) + 5d / 16d * Math.Pow(dn, 3) + 41d / 180d * Math.Pow(dn, 4) - 127d / 288d * Math.Pow(dn, 5);
            dal[2] = 13d / 48d * Math.Pow(dn, 2) - 3d / 5d * Math.Pow(dn, 3) + 557d / 1440d * Math.Pow(dn, 4) + 281d / 630d * Math.Pow(dn, 5);
            dal[3] = 61d / 240d * Math.Pow(dn, 3) - 103d / 140d * Math.Pow(dn, 4) + 15061d / 26880d * Math.Pow(dn, 5);
            dal[4] = 49561d / 161280d * Math.Pow(dn, 4) - 179d / 168d * Math.Pow(dn, 5);
            dal[5] = 34729d / 80640d * Math.Pow(dn, 5);
            double dSg = 0; double dTu = 0;
            for (int j = 1; j <= 5; j++)
            {
                dSg = dSg + 2 * j * dal[j] * Math.Cos(2 * j * dXi) * Math.Cosh(2 * j * dEt);
                dTu = dTu + 2 * j * dal[j] * Math.Sin(2 * j * dXi) * Math.Sinh(2 * j * dEt);
            }
            dSg = 1 + dSg;

            //A0-A5
            double[] dA = new double[6];
            dA[0] = 1 + Math.Pow(dn, 2) / 4 + Math.Pow(dn, 4) / 64;
            dA[1] = -3d / 2d * (dn - Math.Pow(dn, 3) / 8 - Math.Pow(dn, 5) / 64);
            dA[2] = 15d / 16d * (Math.Pow(dn, 2) - Math.Pow(dn, 4) / 4);
            dA[3] = -35d / 48d * (Math.Pow(dn, 3) - 5d / 16d * Math.Pow(dn, 5));
            dA[4] = 315d / 512d * Math.Pow(dn, 4);
            dA[5] = -693d / 1280d * Math.Pow(dn, 5);
            double dAb = dM0 * daa / (1 + dn) * dA[0];
            double dSb = 0;
            for (int j = 1; j <= 5; j++)
            {
                dSb = dSb + dA[j] * Math.Sin(2 * j * Lat0);
            }
            dSb = dM0 * daa / (1 + dn) * (dA[0] * Lat0 + dSb);

            Y = 0; X = 0;
            for (int j = 1; j <= 5; j++)
            {
                Y = Y + dal[j] * Math.Sin(2 * j * dXi) * Math.Cosh(2 * j * dEt);
                X = X + dal[j] * Math.Cos(2 * j * dXi) * Math.Sinh(2 * j * dEt);
            }
            Y = dAb * (dXi + Y) - dSb;
            X = dAb * (dEt + X);
        }

        /// <summary>
        /// 平面直角座標から緯度・経度を求める
        /// </summary>
        /// <param name="X">X座標(東西方向、m)</param>
        /// <param name="Y">Y座標(南北方向、m)</param>
        /// <param name="Lon0">原点:経度[度]</param>
        /// <param name="Lat0">原点:緯度[度]</param>
        /// <param name="Lon">経度[度]</param>
        /// <param name="Lat">緯度[度]</param>
        public static void XY2LonLat(double X, double Y, double Lon0, double Lat0, out double Lon, out double Lat)
        {
            double dn = 1d / (2 * dF - 1);

            //ラジアン単位に
            Lon0 = Deg2Rad(Lon0);
            Lat0 = Deg2Rad(Lat0);


            //Sφ0、A
            double[] dA = new double[6];
            dA[0] = 1 + Math.Pow(dn, 2) / 4 + Math.Pow(dn, 4) / 64;
            dA[1] = -3d / 2d * (dn - Math.Pow(dn, 3) / 8 - Math.Pow(dn, 5) / 64);
            dA[2] = 15d / 16d * (Math.Pow(dn, 2) - Math.Pow(dn, 4) / 4);
            dA[3] = -35d / 48d * (Math.Pow(dn, 3) - 5d / 16d * Math.Pow(dn, 5));
            dA[4] = 315d / 512d * Math.Pow(dn, 4);
            dA[5] = -693d / 1280d * Math.Pow(dn, 5);
            double dAb = dM0 * daa / (1 + dn) * dA[0];
            double dSb = 0;
            for (int j = 1; j <= 5; j++)
            {
                dSb = dSb + dA[j] * Math.Sin(2 * j * Lat0);
            }
            dSb = dM0 * daa / (1 + dn) * (dA[0] * Lat0 + dSb);

            //ξ・η
            double dXi = (Y + dSb) / dAb;
            double dEt = X / dAb;

            //β
            double[] dBt = new double[6];
            dBt[1] = 1d / 2d * dn - 2d / 3d * Math.Pow(dn, 2) + 37d / 96d * Math.Pow(dn, 3) - 1d / 360d * Math.Pow(dn, 4) - 81d / 512d * Math.Pow(dn, 5);
            dBt[2] = 1d / 48d * Math.Pow(dn, 2) + 1d / 15d * Math.Pow(dn, 3) - 437d / 1440d * Math.Pow(dn, 4) + 46d / 105d * Math.Pow(dn, 5);
            dBt[3] = 17d / 480d * Math.Pow(dn, 3) - 37d / 840d * Math.Pow(dn, 4) - 209d / 4480d * Math.Pow(dn, 5);
            dBt[4] = 4397d / 161280d * Math.Pow(dn, 4) - 11d / 504d * Math.Pow(dn, 5);
            dBt[5] = 4583d / 161280d * Math.Pow(dn, 5);

            //ξ’・η'・σ'・τ'・χ
            double dXi2 = 0;
            double dEt2 = 0;
            double dSg2 = 0;
            double dTu2 = 0;
            for (int j = 1; j <= 5; j++)
            {
                dXi2 = dXi2 + dBt[j] * Math.Sin(2 * j * dXi) * Math.Cosh(2 * j * dEt);
                dEt2 = dEt2 + dBt[j] * Math.Cos(2 * j * dXi) * Math.Sinh(2 * j * dEt);
                dSg2 = dSg2 + dBt[j] * Math.Cos(2 * j * dXi) * Math.Cosh(2 * j * dEt);
                dTu2 = dTu2 + dBt[j] * Math.Sin(2 * j * dXi) * Math.Sinh(2 * j * dEt);
            }
            dXi2 = dXi - dXi2;
            dEt2 = dEt - dEt2;
            dSg2 = 1 - dSg2;
            double dCi = Math.Asin(Math.Sin(dXi2) / Math.Cosh(dEt2));

            //δ
            double[] dDt = new double[7];
            dDt[1] = 2 * dn - 2d / 3d * Math.Pow(dn, 2) - 2 * Math.Pow(dn, 3) + 116d / 45d * Math.Pow(dn, 4) + 26d / 45d * Math.Pow(dn, 5) - 2854d / 675d * Math.Pow(dn, 6);
            dDt[2] = 7d / 3d * Math.Pow(dn, 2) - 8d / 5d * Math.Pow(dn, 3) - 227d / 45d * Math.Pow(dn, 4) + 2704d / 315d * Math.Pow(dn, 5) + 2323d / 945d * Math.Pow(dn, 6);
            dDt[3] = 56d / 15d * Math.Pow(dn, 3) - 136d / 35d * Math.Pow(dn, 4) - 1262d / 105d * Math.Pow(dn, 5) + 73814d / 2835d * Math.Pow(dn, 6);
            dDt[4] = 4279d / 630d * Math.Pow(dn, 4) - 332d / 35d * Math.Pow(dn, 5) - 399572d / 14175d * Math.Pow(dn, 6);
            dDt[5] = 4174d / 315d * Math.Pow(dn, 5) - 144838d / 6237d * Math.Pow(dn, 6);
            dDt[6] = 601676d / 22275d * Math.Pow(dn, 6);

            //ラジアン単位の緯度経度
            Lon = Lon0 + Math.Atan(Math.Sinh(dEt2) / Math.Cos(dXi2));
            Lat = dCi;
            for (int j = 1; j <= 6; j++)
            {
                Lat = Lat + dDt[j] * Math.Sin(2 * j * dCi);
            }

            //度単位に
            Lon = 180 * Lon / Math.PI;
            Lat = 180 * Lat / Math.PI;
        }

        //双曲線正接関数の逆関数
        private static double atanh(double x)
        {
            return (1d / 2d * Math.Log((1 + x) / (1 - x), Math.E));
        }
        private static double Deg2Rad(double Deg)
        {
            return (Math.PI * Deg / 180d);
        }
    }
}

脚注   [ + ]

1. 平成十四年国土交通省告示第九号
2. ”The y value, called the northing” Handbook for Transformation of
Datums…
p.44