将像素转换为Google静态图像的地理坐标(LatLng Coordinates)

9

我正在从谷歌静态地图API加载一张图片,这张卫星图像显示的是一个数百米宽长的区域。

https://maps.googleapis.com/maps/api/staticmap?center=53.4055429,-2.9976502&zoom=16&size=400x400&maptype=satellite&key=YOUR_API_KEY

此外,如下所示,图像分辨率显示为10米:

enter image description here.

enter image description here

我的问题是:我已知道静态图像的中心地理位置 (53.4055429,-2.9976502) 和分辨率,如何将其扩展以计算图像左上或右下的地理位置,并最终计算图像的每个像素?

是的,center= 参数后面跟随的坐标是地图的中心 (center=53.4055429,-2.9976502)。你认为它们不是吗? - geocodezip
只是确认一下。我该如何获取每个像素的坐标,这是否可能? - user824624
需要使用静态地图吗?JavaScript API的CanvasProjection有一个可以将像素位置转换为经纬度的功能。https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapCanvasProjection - Paul Thomas
你可以试试我的答案。其实,你不需要使用Google Maps JS API来进行简单的数学计算,比如幂和余弦。请注意,这两个答案在输出控制台中的结果相同。 - shukshin.ivan
你应该选择最佳答案,否则你的悬赏将白白消失。 - shukshin.ivan
2个回答

16

这是什么样的解决方案

看起来你不需要JavaScript的解决方案,而是需要使用Python在服务器上进行操作。我已经创建了一个Python示例,但我将依靠数学计算,数学是计算坐标所需的全部内容。让我也用JS做一下,以使片段在浏览器中正常工作。你会发现,Python和JS给出相同的结果。

跳转到答案

如果你只需要每像素度数的公式,那么这里有。它们足够简单,你不需要任何外部库,只需要Python的math。详细的解释可以在下面找到。

#!/usr/bin/python
import math

w = 400
h = 400
zoom = 16
lat = 53.4055429
lng = -2.9976502

def getPointLatLng(x, y):
    parallelMultiplier = math.cos(lat * math.pi / 180)
    degreesPerPixelX = 360 / math.pow(2, zoom + 8)
    degreesPerPixelY = 360 / math.pow(2, zoom + 8) * parallelMultiplier
    pointLat = lat - degreesPerPixelY * ( y - h / 2)
    pointLng = lng + degreesPerPixelX * ( x  - w / 2)

    return (pointLat, pointLng)

print 'NE: ', getPointLatLng(w, 0)
print 'SW: ', getPointLatLng(0, h)
print 'NW: ', getPointLatLng(0, 0)
print 'SE: ', getPointLatLng(w, h)

这个脚本的输出结果是:

$ python getcoords.py
NE:  (53.40810128625675, -2.9933586655761717)
SW:  (53.40298451374325, -3.001941734423828)
NW:  (53.40810128625675, -3.001941734423828)
SE:  (53.40298451374325, -2.9933586655761717)

我们需要开始的内容

我们有一些在URL中需要的参数https://maps.googleapis.com/maps/api/staticmap?center=53.4055429,-2.9976502&zoom=16&size=400x400&maptype=satellite&key=YOUR_API_KEY —— 坐标、缩放比例、像素大小。

让我们引入一些初始变量:

var config = {
    lat: 53.4055429,
    lng: -2.9976502,
    zoom: 16,
    size: {
        x: 400,
        y: 400,
    }
};

512像素地球的数学

计算如下。当使用图像大小为512(请参阅文档以获取大小和缩放比例)时,缩放等级1代表完整查看地球赤道的360°。在缩放等级1的示例中可以看到这一点。这是非常重要的一点。比例尺(每像素度数)不取决于图像大小。当更改图像大小时,比例尺保持不变:请比较12——第二个图像是一个裁剪版本的大图像。对于googleapis的最大图像大小为640

每次缩小将分辨率增加两倍。因此,根据经度计算出图像的宽度为

lngDegrees = 360 / 2**(zoom - 1); // full image width in degrees, ** for power

然后使用线性函数来找到图像中任意点的坐标。需要指出的是,线性仅适用于高缩放比例的图像,对于低于5倍缩放的图像,不能使用线性函数,此时数学稍微复杂一些。

lngDegreesPerPixel = lngDegrees / 512 = 360 / 2**(zoom - 1) / 2**9 = 360 / 2**(zoom + 8); 
lngX = config.lng + lngDegreesPerPixel * ( point.x - config.size.x / 2);

纬度度数不同

在赤道上,纬度和经度的度数大小相同,但是如果我们向北或向南前进,由于地球上的纬线圈半径较小,因此经度度数会变得更小 - r = R * cos(lat) < R,因此图像高度以度为单位变得更小(参见P.S.)。

latDegrees = 360 / 2**(zoom - 1) * cos(lat); // full image height in degrees, ** for power

分别为:

latDegreesPerPixel = latDegrees / 512 = 360 / 2**(zoom - 1) * cos(lat) / 2**9 = 360 / 2**(zoom + 8) * cos(lat);
latY = config.lat - latDegreesPerPixel * ( point.y - config.size.y / 2)

config.lat的符号与lngX的符号不同,因为地球的经度方向与图像x方向重合,但是纬度方向与图像y方向相反。

因此,我们现在可以编写一个简单的函数,使用图片上的xy坐标来查找像素的坐标。

var config = {
    lat: 53.4055429,
    lng: -2.9976502,
    zoom: 16,
    size: {
        x: 400,
        y: 400,
    }
};

function getCoordinates(x, y) {
    var degreesPerPixelX = 360 / Math.pow(2, config.zoom + 8);
    var degreesPerPixelY = 360 / Math.pow(2, config.zoom + 8) * Math.cos(config.lat * Math.PI / 180);

    return {
        lat: config.lat - degreesPerPixelY * ( y - config.size.y / 2),
        lng: config.lng + degreesPerPixelX * ( x  - config.size.x / 2),
    };
}

console.log('SW', getCoordinates(0, config.size.y));
console.log('NE', getCoordinates(config.size.x, 0));
console.log('SE', getCoordinates(config.size.x, config.size.y));
console.log('NW', getCoordinates(0, 0));
console.log('Something at 300,128', getCoordinates(300, 128));

顺便说一句,你可能会问我为什么在纬度公式中使用 cos(lat) 作为乘数,而不是将其作为除数用于经度公式。我发现谷歌会选择在不同纬度上每个像素保持恒定的经度比例尺,因此,cos 作为乘数被应用于纬度。


1
这些公式适用于像素的左上角。将函数的参数加上 0.5,即可得到像素中心的结果。 - shukshin.ivan
我正在使用上述公式来计算任何给定坐标的点/(x,y),但没有成功。 - Ashwin Khadgi
点 getPointFromCoordinates(double lat, double lng) { double degreesPerPixelX = 360.0 / Math.pow(2, zoom + 8); double degreesPerPixelY = 360.0 / Math.pow(2, zoom + 8) * Math.cos(centerLat * Math.PI / 180.0); 点 point = new 点(); point.y = (int) ((centerLat - lat) / degreesPerPixelY) + (size.y / 2); point.x = (int) ((centerLng - lng) / degreesPerPixelX) + (size.x / 2); 返回 point; } - Ashwin Khadgi

3
我相信您可以使用Maps JavaScript API计算边界框。
您有一个中心位置,并且知道从中心到东北和西南的距离为200像素,因为您的示例大小为400x400。
请查看以下代码以计算NE和SW点。

var map;
function initMap() {
  var latLng = new google.maps.LatLng(53.4055429,-2.9976502);

  map = new google.maps.Map(document.getElementById('map'), {
      center: latLng,
      zoom: 16,
      mapTypeId: google.maps.MapTypeId.SATELLITE
  });

  var marker = new google.maps.Marker({
      position: latLng,
      map: map
  });

  google.maps.event.addListener(map, "idle", function() {
      //Verical and horizontal distance from center in pixels
      var h = 200;
      var w = 200;

      var centerPixel = map.getProjection().fromLatLngToPoint(latLng);
      var pixelSize = Math.pow(2, -map.getZoom());

      var nePoint = new google.maps.Point(centerPixel.x + w*pixelSize, centerPixel.y - h*pixelSize);
      var swPoint = new google.maps.Point(centerPixel.x - w*pixelSize, centerPixel.y + h*pixelSize);

      var ne = map.getProjection().fromPointToLatLng(nePoint);
      var sw = map.getProjection().fromPointToLatLng(swPoint);

      var neMarker = new google.maps.Marker({
        position: ne,
        map: map,
        title: "NE: " + ne.toString()
      });

      var swMarker = new google.maps.Marker({
        position: sw,
        map: map,
        title: "SW: " + sw.toString()
      });

      var polygon = new google.maps.Polygon({
          paths: [ne, new google.maps.LatLng(ne.lat(),sw.lng()), sw, new google.maps.LatLng(sw.lat(),ne.lng())],
          map: map, 
          strokeColor: "green"
      });

      console.log("NE: " + ne.toString());
      console.log("SW: " + sw.toString());

  });
}
#map {
  height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDztlrk_3CnzGHo7CFvLFqE_2bUKEq1JEU&libraries=geometry&callback=initMap"
    async defer></script>

我希望这能有所帮助!

更新

为了在Python中解决这个问题,您应该了解Google Maps JavaScript API使用的地图和瓦片坐标原理,并实现类似于Google Maps API的投影逻辑。

幸运的是,有人已经完成了这个任务,您可以在github上找到实现类似于我的示例中map.getProjection().fromLatLngToPoint()map.getProjection().fromPointToLatLng()方法的项目。请查看这个项目:

https://github.com/hrldcpr/mercator.py

所以,您可以下载mercator.py并在项目中使用。我的JavaScript API示例转换为以下python代码。
#!/usr/bin/python

from mercator import *

w = 200
h = 200
zoom = 16
lat = 53.4055429
lng = -2.9976502

centerPixel = get_lat_lng_tile(lat, lng, zoom)
pixelSize = pow(2, -zoom)

nePoint = (centerPixel[0] + w*pixelSize, centerPixel[1] - h*pixelSize)
swPoint = (centerPixel[0] - w*pixelSize, centerPixel[1] + h*pixelSize)

ne = get_tile_lat_lng(zoom, nePoint[0], nePoint[1]);
sw = get_tile_lat_lng(zoom, swPoint[0], swPoint[1]);

print 'NorthEast: ', ne
print 'SouthWest: ', sw 

我正在使用Python,那么它会是基于Python的API吗? - user824624
你的Python结果肯定是有问题的,与js结果不同。 - shukshin.ivan
@shukshin.ivan 感谢您的评论。我没有彻底测试过mercator.py,实际上,实现可能不够精确,但它展示了这个想法。一旦我有空闲时间,我会再看一下。 - xomena
我所说的不是精度,而是误差。两者之间的差距非常大,超过了一百倍。 - shukshin.ivan
无论如何,看起来作者忘记了他的问题) - shukshin.ivan
我已经找到了你的结果和正确结果之间的差异。它们相差512倍。mercator文档应该提到需要指定原始宽度 :) 但我仍然不明白为什么要使用外部库而不是余弦和幂函数。祝好运。奇怪的赏金消失了。 - shukshin.ivan

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