从谷歌静态地图中的像素坐标获取经度/纬度

Posted

技术标签:

【中文标题】从谷歌静态地图中的像素坐标获取经度/纬度【英文标题】:Getting lon/lat from pixel coords in Google Static Map 【发布时间】:2012-05-13 14:22:24 【问题描述】:

我有一个使用 Google 静态地图的 JAVA 项目,经过数小时的工作后,我无法正常工作,我将解释一切,希望有人能够帮助我。

我使用的是静态地图(480 像素 x 480 像素),地图的中心是 lat=47,lon=1.5,缩放级别是 5。

现在我需要的是当我点击这个静态地图上的一个像素时能够得到纬度和经度。经过一番搜索,我发现我应该使用墨卡托投影(对吗?),我还发现每个缩放级别都会使水平和垂直维度的精度翻倍,但我找不到正确的公式来链接像素、缩放级别和纬度/隆...

我的问题只是从像素中获取纬度/经度,了解中心的坐标和像素以及缩放级别...

提前谢谢你!

【问题讨论】:

您有链接到您了解墨卡托投影的页面吗? 【参考方案1】:

Google-maps 使用地图切片来有效地将世界划分为 256^21 像素切片的网格。基本上,这个世界是由 4 个最小缩放的瓷砖组成的。当您开始缩放时,您会得到 16 个图块,然后是 64 个图块,然后是 256 个图块。它基本上是一个四叉树。因为这样的 1d 结构只能展平 2d,所以您还需要 Mercantor 投影或转换为 WGS 84。这是一个很好的资源 Convert long/lat to pixel x/y on a given picture。谷歌地图中有将经纬度对转换为像素的功能。这是一个链接,但它说瓷砖只有 128x128:http://michal.guerquin.com/googlemaps.html

    Google Maps V3 - How to calculate the zoom level for a given bounds http://www.physicsforums.com/showthread.php?t=455491

【讨论】:

事实上,我并不“关心”瓷砖,我正在用 Java 做这个项目,所以我只有一张我知道它的大小(480 像素 x 480 像素)的图片,我知道它的中心坐标(lat=47,lon=1.5 所以中心的像素是 240x240 )和它的缩放级别(5)。我所需要的只是获取任何像素坐标的公式...在此先感谢【参考方案2】:

使用墨卡托投影。

如果你通过[0,256]投影到[0, 256)的空间:

LatLng(47,=1.5) is Point(129.06666666666666, 90.04191318303863)

在缩放级别 5 时,这些等同于像素坐标:

x = 129.06666666666666 * 2^5 = 4130
y = 90.04191318303863 * 2^5 = 2881

因此,您的地图左上角位于:

x = 4130 - 480/2 = 4070
y = 2881 - 480/2 = 2641

4070 / 2^5 = 127.1875
2641 / 2^5 = 82.53125

最后:

Point(127.1875, 82.53125) is LatLng(53.72271667491848, -1.142578125)

【讨论】:

感谢您的回复,但我认为有问题。事实上,你说我的左上角在英国的 LatLng(53.72271667491848, -1.142578125) 但事实上,我的地图的左上角更多的是在爱尔兰,看看:i50.tinypic.com/9vb2b7.jpg 你可以看到我的窗口,-地图是以 LatLng(47,1.5) 为中心 - 地图大小为 480x480 - 地图的缩放级别为 5 --> 所以我猜 LatLng(47,1.5) == Point(240,240) 不是吗? (当我将鼠标放在 Point(240,240) 上时似乎是真的,我真的很接近 LatLng(47,1.5) )提前谢谢 @user1374021:旧线程,我知道,但我正在解决这个问题,这个答案很有帮助。问题在于计算 x 的数学;它应该是 x = 4130 - 480/2 = 3890。y=2641 是正确的。当您将这些数字推入时,您将获得与您的图像匹配的 -9.00 的 lon。【参考方案3】:

根据上面 Chris Broadfoot 的回答和 some other code on Stack Overflow for the Mercator Projection 中的数学计算,我得到了这个

public class MercatorProjection implements Projection 

    private static final double DEFAULT_PROJECTION_WIDTH = 256;
    private static final double DEFAULT_PROJECTION_HEIGHT = 256;

    private double centerLatitude;
    private double centerLongitude;
    private int areaWidthPx;
    private int areaHeightPx;
    // the scale that we would need for the a projection to fit the given area into a world view (1 = global, expect it to be > 1)
    private double areaScale;

    private double projectionWidth;
    private double projectionHeight;
    private double pixelsPerLonDegree;
    private double pixelsPerLonRadian;

    private double projectionCenterPx;
    private double projectionCenterPy;

    public MercatorProjection(
            double centerLatitude,
            double centerLongitude,
            int areaWidthPx,
            int areaHeightPx,
            double areaScale
    ) 
        this.centerLatitude = centerLatitude;
        this.centerLongitude = centerLongitude;
        this.areaWidthPx = areaWidthPx;
        this.areaHeightPx = areaHeightPx;
        this.areaScale = areaScale;

        // TODO stretch the projection to match to deformity at the center lat/lon?
        this.projectionWidth = DEFAULT_PROJECTION_WIDTH;
        this.projectionHeight = DEFAULT_PROJECTION_HEIGHT;
        this.pixelsPerLonDegree = this.projectionWidth / 360;
        this.pixelsPerLonRadian = this.projectionWidth / (2 * Math.PI);

        Point centerPoint = projectLocation(this.centerLatitude, this.centerLongitude);
        this.projectionCenterPx = centerPoint.x * this.areaScale;
        this.projectionCenterPy = centerPoint.y * this.areaScale;
    

    @Override
    public Location getLocation(int px, int py) 
        double x = this.projectionCenterPx + (px - this.areaWidthPx / 2);
        double y = this.projectionCenterPy + (py - this.areaHeightPx / 2);

        return projectPx(x / this.areaScale, y / this.areaScale);
    

    @Override
    public Point getPoint(double latitude, double longitude) 
        Point point = projectLocation(latitude, longitude);

        double x = (point.x * this.areaScale - this.projectionCenterPx) + this.areaWidthPx / 2;
        double y = (point.y * this.areaScale - this.projectionCenterPy) + this.areaHeightPx / 2;

        return new Point(x, y);
    

    // from https://***.com/questions/12507274/how-to-get-bounds-of-a-google-static-map

    Location projectPx(double px, double py) 
        final double longitude = (px - this.projectionWidth/2) / this.pixelsPerLonDegree;
        final double latitudeRadians = (py - this.projectionHeight/2) / -this.pixelsPerLonRadian;
        final double latitude = rad2deg(2 * Math.atan(Math.exp(latitudeRadians)) - Math.PI / 2);
        return new Location() 
            @Override
            public double getLatitude() 
                return latitude;
            

            @Override
            public double getLongitude() 
                return longitude;
            
        ;
    

    Point projectLocation(double latitude, double longitude) 
        double px = this.projectionWidth / 2 + longitude * this.pixelsPerLonDegree;
        double siny = Math.sin(deg2rad(latitude));
        double py = this.projectionHeight / 2 + 0.5 * Math.log((1 + siny) / (1 - siny) ) * -this.pixelsPerLonRadian;
        Point result = new org.opencv.core.Point(px, py);
        return result;
    

    private double rad2deg(double rad) 
        return (rad * 180) / Math.PI;
    

    private double deg2rad(double deg) 
        return (deg * Math.PI) / 180;
    

这是原始答案的单元测试

public class MercatorProjectionTest 

    @Test
    public void testExample() 

        // tests against values in https://***.com/questions/10442066/getting-lon-lat-from-pixel-coords-in-google-static-map

        double centerLatitude = 47;
        double centerLongitude = 1.5;

        int areaWidth = 480;
        int areaHeight = 480;

        // google (static) maps zoom level
        int zoom = 5;

        MercatorProjection projection = new MercatorProjection(
                centerLatitude,
                centerLongitude,
                areaWidth,
                areaHeight,
                Math.pow(2, zoom)
        );

        Point centerPoint = projection.projectLocation(centerLatitude, centerLongitude);
        Assert.assertEquals(129.06666666666666, centerPoint.x, 0.001);
        Assert.assertEquals(90.04191318303863, centerPoint.y, 0.001);

        Location topLeftByProjection = projection.projectPx(127.1875, 82.53125);
        Assert.assertEquals(53.72271667491848, topLeftByProjection.getLatitude(), 0.001);
        Assert.assertEquals(-1.142578125, topLeftByProjection.getLongitude(), 0.001);

        // NOTE sample has some pretty serious rounding errors
        Location topLeftByPixel = projection.getLocation(0, 0);
        Assert.assertEquals(53.72271667491848, topLeftByPixel.getLatitude(), 0.05);
        // the math for this is wrong in the sample (see comments)
        Assert.assertEquals(-9, topLeftByPixel.getLongitude(), 0.05);

        Point reverseTopLeftBase = projection.projectLocation(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
        Assert.assertEquals(121.5625, reverseTopLeftBase.x, 0.1);
        Assert.assertEquals(82.53125, reverseTopLeftBase.y, 0.1);

        Point reverseTopLeft = projection.getPoint(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
        Assert.assertEquals(0, reverseTopLeft.x, 0.001);
        Assert.assertEquals(0, reverseTopLeft.y, 0.001);

        Location bottomRightLocation = projection.getLocation(areaWidth, areaHeight);
        Point bottomRight = projection.getPoint(bottomRightLocation.getLatitude(), bottomRightLocation.getLongitude());
        Assert.assertEquals(areaWidth, bottomRight.x, 0.001);
        Assert.assertEquals(areaHeight, bottomRight.y, 0.001);
    


如果您(例如)使用航空摄影,我觉得该算法没有考虑墨卡托投影的拉伸效果,因此如果您的感兴趣区域不是相对接近,它可能会失去准确性赤道。我想您可以通过将 x 坐标乘以中心的 cos(latitude) 来近似它?

【讨论】:

【参考方案4】:

似乎值得一提的是,您实际上可以让 google maps API 根据像素坐标为您提供纬度和经度坐标。

虽然在 V3 中有点复杂,但这里有一个示例说明如何做到这一点。 (注意:这是假设您已经有一张地图和要转换为 lat&lng 坐标的像素顶点):

let overlay  = new google.maps.OverlayView();
overlay.draw = function() ;
overlay.onAdd = function() ;
overlay.onRemove = function() ;
overlay.setMap(map);

let latlngObj = overlay.fromContainerPixelToLatLng(new google.maps.Point(pixelVertex.x, pixelVertex.y);

overlay.setMap(null); //removes the overlay

希望对某人有所帮助。

更新:我意识到我做了这两种方式,两者仍然使用相同的方式来创建覆盖(所以我不会复制那个代码)。

let point = new google.maps.Point(628.4160703464878, 244.02779437950872);
console.log(point);
let overlayProj = overlay.getProjection();
console.log(overlayProj);
let latLngVar = overlayProj.fromContainerPixelToLatLng(point);
console.log('the latitude is: '+latLngVar.lat()+' the longitude is: '+latLngVar.lng());

【讨论】:

以上是关于从谷歌静态地图中的像素坐标获取经度/纬度的主要内容,如果未能解决你的问题,请参考以下文章

从谷歌标记获取纬度/经度

如何从谷歌地图的经纬度坐标中获取城市名称?

如何使用 ionic 中的 java 脚本从谷歌地图中的地址获取经度和纬度

如何将地理坐标转换为像素?

从谷歌地图获取纬度和经度

从谷歌地图获取经度和纬度的不断更新[重复]