使用Python无浏览器捕获嵌入的谷歌地图图像
我注意到,从谷歌地图页面上,你可以获取一个“嵌入”链接,把它放进一个iframe里,这样就能在浏览器中加载地图。(这没什么新鲜的)
图片的大小可以调整得很大,所以我想获取一些大的单张PNG图片。
更具体来说,我想从一个矩形区域(上右和下左的坐标)定义一个边界框,然后获取对应的图像,并设置合适的缩放比例。
但我想问的是:我怎么用Python获取这个地图的“像素内容”,作为一个图像对象呢?
(我的想法是:如果浏览器能获取并显示这样的图像内容,那么Python也应该能做到这一点。)
编辑:这是显示我示例地图的HTML文件内容:
<iframe
width="2000"
height="1500"
frameborder="0"
scrolling="yes"
marginheight="0"
marginwidth="0"
src="http://maps.google.com.br/maps?hl=pt-BR&ll=-30.027489,-51.229248&spn=1.783415,2.745209&z=10&output=embed"/>
编辑:我按照Ned Batchelder的建议,使用从上面的iframe获取的src
地址,读取了urllib.urlopen()
调用的内容。结果得到了一大堆JavaScript代码,我觉得这和谷歌地图的JavaScript API有关。所以,问题依然存在:我该如何在Python中利用这些内容来获取地图图像呢?
编辑:这个链接似乎包含了一些关于谷歌地图如何切片的相关信息: http://www.codeproject.com/KB/scrapbook/googlemap.aspx
8 个回答
编辑: 这个回答中的代码已经被改进和简化,可以在这里找到:https://stackoverflow.com/a/50536888/5859283
根据heltonbiker的优秀回答和BenElgar的修改,下面是一些更新的Python 3代码,并增加了API密钥的访问,希望对某些人有用:
"""
Stitch together Google Maps images from lat, long coordinates
Based on work by heltonbiker and BenElgar
Changes:
* updated for Python 3
* added Google Cloud Static Maps API key field (now required for access)
* handle http request exceptions
"""
import requests
from io import BytesIO
from math import log, exp, tan, atan, pi, ceil
from PIL import Image
import sys
EARTH_RADIUS = 6378137
EQUATOR_CIRCUMFERENCE = 2 * pi * EARTH_RADIUS
INITIAL_RESOLUTION = EQUATOR_CIRCUMFERENCE / 256.0
ORIGIN_SHIFT = EQUATOR_CIRCUMFERENCE / 2.0
GOOGLE_MAPS_API_KEY = 'change this to your API key'
def latlontopixels(lat, lon, zoom):
mx = (lon * ORIGIN_SHIFT) / 180.0
my = log(tan((90 + lat) * pi/360.0))/(pi/180.0)
my = (my * ORIGIN_SHIFT) /180.0
res = INITIAL_RESOLUTION / (2**zoom)
px = (mx + ORIGIN_SHIFT) / res
py = (my + ORIGIN_SHIFT) / res
return px, py
def pixelstolatlon(px, py, zoom):
res = INITIAL_RESOLUTION / (2**zoom)
mx = px * res - ORIGIN_SHIFT
my = py * res - ORIGIN_SHIFT
lat = (my / ORIGIN_SHIFT) * 180.0
lat = 180 / pi * (2*atan(exp(lat*pi/180.0)) - pi/2.0)
lon = (mx / ORIGIN_SHIFT) * 180.0
return lat, lon
def get_maps_image(NW_lat_long, SE_lat_long, zoom=18):
ullat, ullon = NW_lat_long
lrlat, lrlon = SE_lat_long
# Set some important parameters
scale = 1
maxsize = 640
# convert all these coordinates to pixels
ulx, uly = latlontopixels(ullat, ullon, zoom)
lrx, lry = latlontopixels(lrlat, lrlon, zoom)
# calculate total pixel dimensions of final image
dx, dy = lrx - ulx, uly - lry
# calculate rows and columns
cols, rows = int(ceil(dx/maxsize)), int(ceil(dy/maxsize))
# calculate pixel dimensions of each small image
bottom = 120
largura = int(ceil(dx/cols))
altura = int(ceil(dy/rows))
alturaplus = altura + bottom
# assemble the image from stitched
final = Image.new("RGB", (int(dx), int(dy)))
for x in range(cols):
for y in range(rows):
dxn = largura * (0.5 + x)
dyn = altura * (0.5 + y)
latn, lonn = pixelstolatlon(ulx + dxn, uly - dyn - bottom/2, zoom)
position = ','.join((str(latn), str(lonn)))
print(x, y, position)
urlparams = {'center': position,
'zoom': str(zoom),
'size': '%dx%d' % (largura, alturaplus),
'maptype': 'satellite',
'sensor': 'false',
'scale': scale}
if GOOGLE_MAPS_API_KEY is not None:
urlparams['key'] = GOOGLE_MAPS_API_KEY
url = 'http://maps.google.com/maps/api/staticmap'
try:
response = requests.get(url, params=urlparams)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(e)
sys.exit(1)
im = Image.open(BytesIO(response.content))
final.paste(im, (int(x*largura), int(y*altura)))
return final
############################################
if __name__ == '__main__':
# a neighbourhood in Lajeado, Brazil:
NW_lat_long = (-29.44,-52.0)
SE_lat_long = (-29.45,-51.98)
zoom = 18 # be careful not to get too many images!
result = get_maps_image(NW_lat_long, SE_lat_long, zoom=18)
result.show()
与其尝试使用嵌入链接,不如直接去使用谷歌的API来获取静态图像。这里有一个链接到谷歌地图静态图像API - 你可以像使用普通的嵌入链接一样,在网址中直接传入经纬度参数。例如:
http://maps.googleapis.com/maps/api/staticmap?center=-30.027489,-51.229248&size=600x600&zoom=14&sensor=false
这个链接会给你一个600x600的街道级别的图像,中心位置就是你上面提供的坐标,看起来是巴西的阿雷格里港。现在你可以按照Ned的建议,使用urlopen
和PIL
:
from cStringIO import StringIO
import Image
import urllib
url = "http://maps.googleapis.com/maps/api/staticmap?center=-30.027489,-51.229248&size=800x800&zoom=14&sensor=false"
buffer = StringIO(urllib.urlopen(url).read())
image = Image.open(buffer)
感谢大家的回答。我最后用另一种方法解决了这个问题,使用了谷歌地图的静态API,并且用了一些公式把坐标空间转换成像素空间,这样我就能得到精确的图片,能够很好地拼接在一起。
如果有人感兴趣,这里有代码。如果对你有帮助,请留言!
=============================
import Image, urllib, StringIO
from math import log, exp, tan, atan, pi, ceil
EARTH_RADIUS = 6378137
EQUATOR_CIRCUMFERENCE = 2 * pi * EARTH_RADIUS
INITIAL_RESOLUTION = EQUATOR_CIRCUMFERENCE / 256.0
ORIGIN_SHIFT = EQUATOR_CIRCUMFERENCE / 2.0
def latlontopixels(lat, lon, zoom):
mx = (lon * ORIGIN_SHIFT) / 180.0
my = log(tan((90 + lat) * pi/360.0))/(pi/180.0)
my = (my * ORIGIN_SHIFT) /180.0
res = INITIAL_RESOLUTION / (2**zoom)
px = (mx + ORIGIN_SHIFT) / res
py = (my + ORIGIN_SHIFT) / res
return px, py
def pixelstolatlon(px, py, zoom):
res = INITIAL_RESOLUTION / (2**zoom)
mx = px * res - ORIGIN_SHIFT
my = py * res - ORIGIN_SHIFT
lat = (my / ORIGIN_SHIFT) * 180.0
lat = 180 / pi * (2*atan(exp(lat*pi/180.0)) - pi/2.0)
lon = (mx / ORIGIN_SHIFT) * 180.0
return lat, lon
############################################
# a neighbourhood in Lajeado, Brazil:
upperleft = '-29.44,-52.0'
lowerright = '-29.45,-51.98'
zoom = 18 # be careful not to get too many images!
############################################
ullat, ullon = map(float, upperleft.split(','))
lrlat, lrlon = map(float, lowerright.split(','))
# Set some important parameters
scale = 1
maxsize = 640
# convert all these coordinates to pixels
ulx, uly = latlontopixels(ullat, ullon, zoom)
lrx, lry = latlontopixels(lrlat, lrlon, zoom)
# calculate total pixel dimensions of final image
dx, dy = lrx - ulx, uly - lry
# calculate rows and columns
cols, rows = int(ceil(dx/maxsize)), int(ceil(dy/maxsize))
# calculate pixel dimensions of each small image
bottom = 120
largura = int(ceil(dx/cols))
altura = int(ceil(dy/rows))
alturaplus = altura + bottom
final = Image.new("RGB", (int(dx), int(dy)))
for x in range(cols):
for y in range(rows):
dxn = largura * (0.5 + x)
dyn = altura * (0.5 + y)
latn, lonn = pixelstolatlon(ulx + dxn, uly - dyn - bottom/2, zoom)
position = ','.join((str(latn), str(lonn)))
print x, y, position
urlparams = urllib.urlencode({'center': position,
'zoom': str(zoom),
'size': '%dx%d' % (largura, alturaplus),
'maptype': 'satellite',
'sensor': 'false',
'scale': scale})
url = 'http://maps.google.com/maps/api/staticmap?' + urlparams
f=urllib.urlopen(url)
im=Image.open(StringIO.StringIO(f.read()))
final.paste(im, (int(x*largura), int(y*altura)))
final.show()