Захват встроенного изображения карты Google с помощью Python без использования браузера
Я заметил, что со страницы Google Maps вы можете получить ссылку "вставить", чтобы помещать внутри iframe и загружать карту в браузере. (здесь нет новостей)
Размер изображения можно настроить так, чтобы он был очень большим, поэтому я заинтересован в том, чтобы получить большие изображения как одиночные .PNG.
В частности, я хотел бы определить прямоугольную область из ограничивающей рамки (верхние и нижние левые координаты) и получить соответствующее изображение с соответствующим коэффициентом масштабирования.
Но мой вопрос: как я могу использовать Python для получения "содержимого пикселя" этой карты в качестве объекта изображения?
(Мое логическое обоснование: если браузер может получать и отображать такой контент изображения, то Python тоже должен это делать).
EDIT: это содержимое файла 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"/>
EDIT: Я сделал, как предложил Нед Батчелдер, и прочитал содержимое вызова urllib.urlopen()
, используя адрес src
, взятый из iframe выше. В результате появилось много javascript-кода, который, как мне кажется, связан с API-интерфейсом API Карт Google. Итак, вопрос задерживается: как я могу сделать полезный материал из всего этого в Python, чтобы получить изображение карты?
EDIT: эта ссылка содержит некоторую довольно актуальную информацию о том, как Карты Google разбивают карты:
http://www.codeproject.com/KB/scrapbook/googlemap.aspx
также:
http://econym.org.uk/gmap/howitworks.htm
Ответы
Ответ 1
Я благодарю за все ответы. Я решил решить проблему другим способом, используя Google Maps Static 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()
Ответ 2
Вместо того, чтобы пытаться использовать ссылку для встраивания, вы должны перейти непосредственно к API Google, чтобы получать изображения в виде статической графики. Здесь ссылка на API статических изображений Google Maps - похоже, вы можете просто передавать параметры длинного/латинского текста в URL так же, как вы это делаете для нормального вложимого. Например:
http://maps.googleapis.com/maps/api/staticmap?center=-30.027489,-51.229248&size=600x600&zoom=14&sensor=false
дает вам обзор уровня улиц размером 600x600, ориентированный на приведенные выше координаты, который, по-видимому, является Порту-Алегри в Бразилии. Теперь вы можете использовать urlopen
и PIL
, как предлагает Ned:
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)
Ответ 3
Это Даниэль Роземан отвечает для людей, которые используют python 3.x:
Код Python 3.x:
from io import BytesIO
from PIL import Image
from urllib import request
import matplotlib.pyplot as plt # this is if you want to plot the map using pyplot
url = "http://maps.googleapis.com/maps/api/staticmap?center=-30.027489,-51.229248&size=800x800&zoom=14&sensor=false"
buffer = BytesIO(request.urlopen(url).read())
image = Image.open(buffer)
# Show Using PIL
image.show()
# Or using pyplot
plt.imshow(image)
plt.show()
Ответ 4
Редактирование:
код в этом ответе был улучшен и упрощен, здесь: fooobar.com/questions/523915/...
Основанный на превосходном ответе 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 Maps API key (compliance with T&C, although can set to None)
* 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 = None # set 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()
Ответ 5
@4Oh4 ответ правильный, но математика намного сложнее, чем нужно. Преобразования между градусами и радианами происходят гораздо чаще, чем нужно. Радиус Земли вызывается без всякой причины - он отменяется во всех расчетах. Смещение добавляется к координатам пикселя без всякой причины. Обрезание логотипа намного больше, чем нужно. И еще несколько разногласий, которые были написаны в изменениях. Вот моя версия:
#!/usr/bin/env python
"""
Stitch together Google Maps images from lat, long coordinates
Based on work by heltonbiker and BenElgar
Changes:
* updated for Python 3
* added Google Maps API key (compliance with T&C, although can set to None)
* handle http request exceptions
With contributions from Eric Toombs.
Changes:
* Dramatically simplified the maths.
* Set a more reasonable default logo cutoff.
* Added global constants for logo cutoff and max image size.
* Translated a couple presumably Portuguese variable names to English.
"""
import requests
from io import BytesIO
from math import log, exp, tan, atan, ceil
from PIL import Image
import sys
# circumference/radius
tau = 6.283185307179586
# One degree in radians, i.e. in the units the machine uses to store angle,
# which is always radians. For converting to and from degrees. See code for
# usage demonstration.
DEGREE = tau/360
ZOOM_OFFSET = 8
GOOGLE_MAPS_API_KEY = None # set to 'your_API_key'
# Max width or height of a single image grabbed from Google.
MAXSIZE = 640
# For cutting off the logos at the bottom of each of the grabbed images. The
# logo height in pixels is assumed to be less than this amount.
LOGO_CUTOFF = 32
def latlon2pixels(lat, lon, zoom):
mx = lon
my = log(tan((lat + tau/4)/2))
res = 2**(zoom + ZOOM_OFFSET) / tau
px = mx*res
py = my*res
return px, py
def pixels2latlon(px, py, zoom):
res = 2**(zoom + ZOOM_OFFSET) / tau
mx = px/res
my = py/res
lon = mx
lat = 2*atan(exp(my)) - tau/4
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
# convert all these coordinates to pixels
ulx, uly = latlon2pixels(ullat, ullon, zoom)
lrx, lry = latlon2pixels(lrlat, lrlon, zoom)
# calculate total pixel dimensions of final image
dx, dy = lrx - ulx, uly - lry
# calculate rows and columns
cols, rows = ceil(dx/MAXSIZE), ceil(dy/MAXSIZE)
# calculate pixel dimensions of each small image
width = ceil(dx/cols)
height = ceil(dy/rows)
heightplus = height + LOGO_CUTOFF
# assemble the image from stitched
final = Image.new('RGB', (int(dx), int(dy)))
for x in range(cols):
for y in range(rows):
dxn = width * (0.5 + x)
dyn = height * (0.5 + y)
latn, lonn = pixels2latlon(
ulx + dxn, uly - dyn - LOGO_CUTOFF/2, zoom)
position = ','.join((str(latn/DEGREE), str(lonn/DEGREE)))
print(x, y, position)
urlparams = {
'center': position,
'zoom': str(zoom),
'size': '%dx%d' % (width, heightplus),
'maptype': 'satellite',
'sensor': 'false',
'scale': 1
}
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*width), int(y*height)))
return final
############################################
if __name__ == '__main__':
# a neighbourhood in Lajeado, Brazil:
NW_lat_long = (-29.44*DEGREE, -52.0*DEGREE)
SE_lat_long = (-29.45*DEGREE, -51.98*DEGREE)
zoom = 18 # be careful not to get too many images!
result = get_maps_image(NW_lat_long, SE_lat_long, zoom=18)
result.show()
Ответ 6
Более сжатый метод совместимости с Python 2.x -
from io import BytesIO
import Image
import urllib
url = "http://maps.googleapis.com/maps/api/staticmap?center=52.50058,13.31316&size=800x800&zoom=14"
buffer = BytesIO(urllib.urlopen(url).read())
image = Image.open(buffer)
image.save("map.png")
Ответ 7
Самый простой способ, чтобы изображение статической карты Google было захвачено/сохранено (в виде png):
import requests
img = open('tmp.png','wb')
img.write(requests.get('https://maps.googleapis.com/maps/api/staticmap?center=33.0456,131.3009&zoom=12&size=320x385&key=YOUR_API_KEY').content)
img.close()
Ответ 8
urllib.urlopen
откроет URL-адрес, результат будет иметь метод .read()
, который вы можете использовать для получения байтов изображения. cStringIO
имеет файл-подобный объект, основанный на строке в памяти. PIL имеет функцию Image.open
, которая открывает файловую вещь, чтобы дать вам объект изображения. Объекты изображения могут быть заданы по их значениям пикселей.