Google Maps 静态 API - 通过中心坐标获取 SW 和 NE

10

我正在使用.NET从Google Maps使用静态地图加载地图瓦片。

我遇到的问题是我不知道返回的图像的SW和NE坐标。

我找到了许多不同的代码示例、公式,但它们似乎都有缺陷。下面这个公式最接近正确答案。当我在Google Maps中输入坐标时,它显示它有一点偏差。

var result = GoogleMapsAPI.GetBounds(new Coordinate(4.79635, 51.15479), 20, 512, 512);

public static class GoogleMapsAPI
{
    public static MapCoordinates GetBounds(Coordinate center, int zoom, int mapWidth, int mapHeight)
    {
        var scale = Math.Pow(2, zoom);

        var SWPoint = new Coordinate(center.X - (mapWidth / 2) / scale, center.Y - (mapHeight / 2) / scale);
        var NEPoint = new Coordinate(center.X + (mapWidth / 2) / scale, center.Y + (mapHeight / 2) / scale);

        return new MapCoordinates() { SouthWest = SWPoint, NorthEast = NEPoint };
    }
}

public class MapCoordinates
{
    public Coordinate SouthWest { get; set; }
    public Coordinate NorthEast { get; set; }
}

public class Coordinate
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public double X { get { return Latitude; } set { Latitude = value; } }
    public double Y { get { return Longitude; } set { Longitude = value; } }

    public Coordinate(double lat, double lng)
    {
        Latitude = lat;
        Longitude = lng;
    }

    public override string ToString()
    {
        return X.ToString() + ", " + Y.ToString();
    }
}

参考资料:
如何获取谷歌静态地图的边界?
http://www.easywms.com/easywms/?q=zh-hans/node/3612

通过中心坐标加载图片:
http://maps.googleapis.com/maps/api/staticmap?center=51.15479,4.79635&zoom=20&size=512x512&sensor=false

错误的 SW:
https://maps.google.be/maps?q=51.154545859375,+4.796105859375&hl=en&ll=51.154763,4.796695&spn=0.000568,0.001635&sll=51.154687,4.796838&sspn=0.001136,0.00327&t=m&z=20

错误的 NE:
https://maps.google.be/maps?q=+51.155034140625,+4.796594140625&hl=en&ll=51.154764,4.796684&spn=0.000568,0.001635&sll=51.154599,4.796723&sspn=0.001136,0.00327&t=m&z=20

1个回答

6

编辑:哇,我才意识到这个问题已经快两年了。非常抱歉。

好的,我觉得这里有几个问题。最重要的是你没有使用你提供的链接中提到的墨卡托投影。你可以在谷歌地图 API 文档中的代码示例中看到其作用。我想你之所以有点接近是因为在缩放级别 20 时比例因子太大,淹没了问题的细节。

要获取边界,你需要将中心的纬度/经度转换为像素坐标,添加/减去以获取所需角落的像素坐标,然后再转换回纬度/经度。第一个链接中的代码可以进行投影和反向投影。以下是我将其翻译为 C# 的代码。

在你上面的解决方案中,你正在使用经度/纬度坐标并添加/减去世界坐标(像素坐标/比例),因此你正在组合两个不同的坐标系。

我在尝试解决这个问题时遇到的一个问题是你的Coordinate类有点令人困惑。希望我没有搞混,但看起来你把纬度和经度搞反了。这很重要,因为在每个方向上墨卡托投影都不同,还有其他原因。你的 X/Y 映射也似乎是反过来的,因为纬度是你的北/南位置,当投影时应该是 Y 坐标,而经度是你的东/西位置,当投影时应该是 X 坐标。

要找到边界,需要使用三种坐标系:经度/纬度、世界坐标和像素坐标。流程是:中心纬度/经度-(墨卡托投影)->中心世界坐标->中心像素坐标->NE/SW像素坐标->NE/SW世界坐标-(反向墨卡托投影)->NE/SW 纬度/经度。

通过添加或减去图像的尺寸/2可以找到像素坐标。像素坐标系统从左上角开始,因此要获得 NE 角,需要从 x 添加宽度/2,并从 y 中减去高度/2。对于 SW 角,需要从 x 减去宽度/2,并从 y 添加高度/2。

以下是 C# 中的投影代码(作为你的 GoogleMapsAPI 类的一部分),翻译自上面的第一个链接的 JavaScript 代码:

static GoogleMapsAPI()
{
    OriginX =  TileSize / 2;
    OriginY =  TileSize / 2;
    PixelsPerLonDegree = TileSize / 360.0;
    PixelsPerLonRadian = TileSize / (2 * Math.PI);
}

public static int TileSize = 256;
public static double OriginX, OriginY;
public static double PixelsPerLonDegree;
public static double PixelsPerLonRadian;

public static double DegreesToRadians(double deg)
{
    return deg * Math.PI / 180.0;
}

public static double RadiansToDegrees(double rads)
{
    return rads * 180.0 / Math.PI;
}

public static double Bound(double value, double min, double max)
{
    value = Math.Min(value, max);
    return Math.Max(value, min);       
}

//From Lat, Lon to World Coordinate X, Y. I'm being explicit in assigning to
//X and Y properties.
public static Coordinate Mercator(double latitude, double longitude)
{
    double siny = Bound(Math.Sin(DegreesToRadians(latitude)), -.9999, .9999);

    Coordinate c = new Coordinate(0,0);
    c.X = OriginX + longitude*PixelsPerLonDegree;
    c.Y = OriginY + .5 * Math.Log((1 + siny) / (1 - siny)) * -PixelsPerLonRadian;

    return c;
}

//From World Coordinate X, Y to Lat, Lon. I'm being explicit in assigning to
//Latitude and Longitude properties.
public static Coordinate InverseMercator(double x, double y)
{      
    Coordinate c = new Coordinate(0, 0);

    c.Longitude = (x - OriginX) / PixelsPerLonDegree;
    double latRadians = (y - OriginY) / -PixelsPerLonRadian;
    c.Latitude = RadiansToDegrees(Math.Atan(Math.Sinh(latRadians)));

    return c;
}

您可以查看原始的JavaScript代码以获取更详细的注释。
我通过手动近似边界并将其与代码给出的答案进行比较来测试它。我对NE角的手动近似值为(51.15501, 4.796695),而代码结果是(51.155005..., 4.797038...),看起来相当接近。SW角的近似值为(51.154572, 4.796007),代码结果是(51.154574..., 4.796006...)
这是一个有趣的问题,希望这能有所帮助!
编辑:意识到我没有包含新的GetBounds函数:
public static MapCoordinates GetBounds(Coordinate center, int zoom, int mapWidth, int mapHeight)
{
    var scale = Math.Pow(2, zoom);

    var centerWorld = Mercator(center.Latitude, center.Longitude);
    var centerPixel = new Coordinate(0, 0);
    centerPixel.X = centerWorld.X * scale;
    centerPixel.Y = centerWorld.Y * scale;

    var NEPixel = new Coordinate(0, 0);
    NEPixel.X = centerPixel.X + mapWidth / 2.0;
    NEPixel.Y = centerPixel.Y - mapHeight / 2.0;

    var SWPixel = new Coordinate(0, 0);
    SWPixel.X = centerPixel.X - mapWidth / 2.0;
    SWPixel.Y = centerPixel.Y + mapHeight / 2.0;

    var NEWorld = new Coordinate(0, 0);
    NEWorld.X = NEPixel.X / scale;
    NEWorld.Y = NEPixel.Y / scale;

    var SWWorld = new Coordinate(0, 0);
    SWWorld.X = SWPixel.X / scale;
    SWWorld.Y = SWPixel.Y / scale;

    var NELatLon = InverseMercator(NEWorld.X, NEWorld.Y);
    var SWLatLon = InverseMercator(SWWorld.X, SWWorld.Y);

    return new MapCoordinates() { NorthEast = NELatLon, SouthWest = SWLatLon };
}

请记住确保您的纬度和经度是正确的:

var result = GoogleMapsAPI.GetBounds(new Coordinate(51.15479, 4.79635), 20, 512, 512);

我知道这个代码并不是最好的,但我希望它很清晰易懂。

非常感谢你写得如此好的答案,但不幸的是,它已经晚了2年 :) - Rik De Peuter
除非我将 Math.Pow(2, zoom) 乘以 2,否则会出现很多错误。我还与其他来源进行了比较,并尝试了所有小的变化。有什么想法是为什么会这样吗? - clankill3r
C# 代码中缺少两个类 / 结构... Coordinate 和 MapCoordinates。 - Jay Dubal
如果您能详细说明您可能采取的其他步骤或对我有什么建议,那将对我非常有帮助。我在这里遇到了瓶颈,必须想办法解决它。如果您需要我提供更多信息,请告诉我。这是完整的类http://pastebin.com/Ab2J8Gcr - Leniaal
更多关于这个问题的信息请点击这里 - Leniaal
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接