百度地図の緯度経度座標からタイル番号を算出する方法

百度地図(バイドゥマップ)における緯度経度座標から対応するタイルの行番号と列番号を計算するアルゴリズムは公式には公開されておらず、関連情報も非常に限られています。

百度地図のJavaScript APIソースコード(難読化済み)を分析することで、その計算プロセスを解明しました。以下に具体的な手順を説明します:

1.難読化されたJavaScript APIコードには、以下の3つの主要な関数が含まれています:

        transformMCToLL: function (mercatorCoord) {
            var result, transformParams;
            result = new b4(Math.abs(mercatorCoord.lng), Math.abs(mercatorCoord.lat));
            for (var i = 0; i < this.MCBAND.length; i++) {
                if (result.lat >= this.MCBAND[i]) {
                    transformParams = this.MC2LL[i];
                    break
                }
            }
            var transformed = this.coordinateTransform(mercatorCoord, transformParams);
            var finalCoord = new b4(transformed.lng.toFixed(6), transformed.lat.toFixed(6));
            return finalCoord
        }
        transformLLToMC: function (llCoord) {
            var result, transformParams;
            llCoord.lng = this.getLoop(llCoord.lng, -180, 180);
            llCoord.lat = this.getRange(llCoord.lat, -74, 74);
            result = new b4(llCoord.lng, llCoord.lat);
            for (var i = 0; i < this.LLBAND.length; i++) {
                if (result.lat >= this.LLBAND[i]) {
                    transformParams = this.LL2MC[i];
                    break
                }
            }
            if (!transformParams) {
                for (var i = this.LLBAND.length - 1; i >= 0; i--) {
                    if (result.lat <= -this.LLBAND[i]) {
                        transformParams = this.LL2MC[i];
                        break
                    }
                }
            }
            var transformed = this.coordinateTransform(llCoord, transformParams);
            var finalCoord = new b4(transformed.lng.toFixed(2), transformed.lat.toFixed(2));
            return finalCoord
        }
        coordinateTransform: function (coord, params) {
            if (!coord || !params) {
                return
            }
            var x = params[0] + params[1] * Math.abs(coord.lng);
            var yFactor = Math.abs(coord.lat) / params[9];
            var y = params[2] + params[3] * yFactor + params[4] * yFactor * yFactor + params[5] * yFactor * yFactor * yFactor + params[6] * yFactor * yFactor * yFactor * yFactor + params[7] * yFactor * yFactor * yFactor * yFactor * yFactor + params[8] * yFactor * yFactor * yFactor * yFactor * yFactor * yFactor;
            x *= (coord.lng < 0 ? -1 : 1);
            y *= (coord.lat < 0 ? -1 : 1);
            return new b4(x, y)
        }

最初の2つの関数は、それぞれメルカトル座標から百度座標への変換、百度座標からメルカトル座標への変換を担当しています。

2.これを基にC#版を実装しました:

//百度地図JavaScript APIを解析して得た座標変換アルゴリズム
private static double[] latitudeBands = { 75, 60, 45, 30, 15, 0 };
private static double[] mercatorBands = { 12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0 };
private static double[][] llToMcParams = {new double[]{-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 26595700718403920, -10725012454188240, 1800819912950474, 82.5}
                                               ,new double[]{0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5}
                                               ,new double[]{0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5}
                                               ,new double[]{0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5}
                                               ,new double[]{-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5}
                                               ,new double[]{-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45}};
private static double[][] mcToLlParams = {new double[]{1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2}
                                               ,new double[]{-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86}
                                               ,new double[]{-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37}
                                               ,new double[]{-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06}
                                               ,new double[]{3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4}
                                               ,new double[]{2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5}};
//緯度経度座標をメルカトル座標に変換
private static PointF ConvertLatLngToMercator(CoordinatePoint point)
{
    double[] params = null;
    double normalizedLat = point.Latitude > 74 ? 74 : point.Latitude;
    normalizedLat = normalizedLat < -74 ? -74 : normalizedLat;
    for (var i = 0; i < latitudeBands.Length; i++) 
    {
        if (point.Latitude >= latitudeBands[i]) 
        {
            params = llToMcParams[i];
            break;
        }
    }
    if (params == null) <br></br>    {
        for (var i = latitudeBands.Length - 1; i >= 0; i--) <br></br>        {
            if (point.Latitude <= -latitudeBands[i]) 
            {
                params = llToMcParams[i];
                break;
            }
        }
    }
    double[] result = ApplyCoordinateTransform(point.Longitude, point.Latitude, params);
    return new PointF((float)result[0], (float)result[1]);
}<br></br>//メルカトル座標を緯度経度座標に変換
private static CoordinatePoint ConvertMercatorToLatLng(PointF mercatorPoint)
{
    double[] params = null;
    PointF normalizedPoint = new PointF(Math.Abs(mercatorPoint.X), Math.Abs(mercatorPoint.Y));
    for (var i = 0; i < mercatorBands.Length; i++) <br></br>    {
        if (normalizedPoint.Y >= mercatorBands[i]) 
        {
            params = mcToLlParams[i];
            break;
        }
   }
   double[] result = ApplyCoordinateTransform(normalizedPoint.X, normalizedPoint.Y, params);
   return new CoordinatePoint(result[0], result[1]);
}
private static double[] ApplyCoordinateTransform(double x, double y, double[] parameters)
{
    var transformedX = parameters[0] + parameters[1] * Math.Abs(x);
    var yFactor = Math.Abs(y) / parameters[9];
    var transformedY = parameters[2] + parameters[3] * yFactor + parameters[4] * yFactor * yFactor + parameters[5] * yFactor * yFactor * yFactor + parameters[6] * yFactor * yFactor * yFactor * yFactor + parameters[7] * yFactor * yFactor * yFactor * yFactor * yFactor + parameters[8] * yFactor * yFactor * yFactor * yFactor * yFactor * yFactor;
    transformedX *= (x < 0 ? -1 : 1);
    transformedY *= (y < 0 ? -1 : 1);
    return new double[] { transformedX, transformedY };
}

3.百度地図の緯度経度座標からメルカトル座標を計算した後、その結果を地図解像度Math.Pow(2,18-zoom)で割ることで平面ピクセル座標を取得できます。さらに、ピクセル座標を256で割ることで、それぞれタイルの行番号と列番号が算出されます。

検証の結果、誤差は0であることが確認できました。

タグ: 百度地図 タイル計算 緯度経度変換 メルカトル投影 C#

5月14日 12:12 投稿