2011-09-20 15 views
10

He notado que, desde la página de Google Maps, puede obtener un enlace "insertar" para poner dentro de un iframe y cargar el mapa en un navegador. (no hay noticias aquí)Capturar imagen de mapa de google incrustada con Python sin usar un navegador

El tamaño de la imagen se puede ajustar para que sea muy grande, por lo que estoy interesado en obtener algunas imágenes grandes como archivos .PNG individuales.

Más específicamente, me gustaría definir un área rectangular desde un cuadro delimitador (coordenadas superior derecha e inferior izquierda), y obtener la imagen correspondiente, con un factor de zoom apropiado.

Pero mi pregunta es: ¿cómo puedo usar Python para obtener el "contenido de píxeles" de este mapa como un objeto de imagen?

(Mi razonamiento es: si el navegador puede obtener y representar dicho contenido de imagen, entonces Python también debería ser capaz de hacerlo).

EDIT: este es el contenido del archivo HTML que muestra mi mapa de ejemplo:

<iframe 
    width="2000" 
    height="1500" 
    frameborder="0" 
    scrolling="yes" 
    marginheight="0" 
    marginwidth="0" 
    src="http://maps.google.com.br/maps?hl=pt-BR&amp;ll=-30.027489,-51.229248&amp;spn=1.783415,2.745209&amp;z=10&amp;output=embed"/> 

EDIT: Yo como se sugiere por Ned Batchelder, y leer el contenido de una llamada urllib.urlopen() utilizando la dirección src tomada del iframe de arriba. El resultado fue una gran cantidad de código de JavaScript, que creo que tiene que ver con la API de JavaScript de Google Maps. Por lo tanto, la pregunta persiste: ¿cómo podría hacer algunas cosas útiles de todo esto en Python para obtener la imagen del mapa?

EDIT: este enlace parece contener algo de información muy relevante sobre cómo Google Maps azulejos sus mapas: http://www.codeproject.com/KB/scrapbook/googlemap.aspx

también: http://econym.org.uk/gmap/howitworks.htm

Respuesta

18

Doy gracias por todas las respuestas. Terminé resolviendo el problema de otra manera, usando la API estática de Google Maps y algunas fórmulas para convertir el espacio de coordenadas en espacio de píxeles, de modo que puedo obtener imágenes precisas que "unen" muy bien.

Para cualquier persona interesada, aquí está el código. Si ayuda a alguien, por favor ¡comenten!

=============================

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() 
+0

¡Muy bien! Lo hice funcionar pero necesitaba hacer algunos pequeños cambios como aquí: https://gist.github.com/BenElgar/0d5b3e7cc89cb2180c6e. Tenga en cuenta que este es Python 2, pero no debería ser demasiado difícil hacerlo funcionar en Python 3. También tenga en cuenta que esto está rompiendo técnicamente los términos de uso de la API de mapas estáticos como se detalla aquí: https: // developers. google.com/maps/terms#section_10_1_3 –

+0

Actualizado para Python 3: https://stackoverflow.com/a/47776466/5859283 – 4Oh4

0

urllib.urlopen se abrirá una URL, el resultado tendrá un método .read() puedes usar para obtener los bytes de la imagen. cStringIO tiene un objeto similar a un archivo basado en una cadena en la memoria. PIL tiene una función Image.open que abre una cosa similar a un archivo para darle un objeto de imagen. Se pueden preguntar a los objetos de imagen sobre sus valores de píxel.

+0

Usar urllib como dijiste me da código JavaScript (creo), no un objeto de imagen. Tiene sentido, ya que el mapa incrustado permite la navegación (no se genera con la API estática de Google Maps, que a su vez tiene serios límites en el tamaño de la imagen). – heltonbiker

+0

@heltonbiker: lo siento, no sé cómo ayudar con el problema más complejo. La descripción original sonó más directa. –

+0

Estoy muy agradecido por sus ideas e interés. En realidad, uso tu sugerencia con la API estática de Google Maps. El problema se plantea, aunque, tal vez se solucione más fácilmente utilizando algún javascript directamente. Si encuentro una buena respuesta, la publicaré aquí. ¡Gracias de nuevo! – heltonbiker

13

En lugar de tratar de utilizar el enlace de inserción, debe ir directamente a la API de Google para obtener imágenes como gráficos estáticos. Aquí está el enlace al Google Maps static image API - parece que puede pasar los parámetros long/lat en la URL tal como lo hace para la línea incrustable normal. Por ejemplo:

http://maps.googleapis.com/maps/api/staticmap?center=-30.027489,-51.229248&size=600x600&zoom=14&sensor=false 

le da una visión general a nivel de calle 600x600 centrado en las coordenadas que das más arriba, que parece ser de Porto Alegre en Brasil. Ahora puede utilizar urlopen y PIL como Ned sugiere:

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) 
+0

Esa es mi alternativa por ahora, pero aún quiero intentar hacer que sea "Plan B", ya que la costura de imágenes sería necesaria para generar esas imágenes de tamaño megapíxel que estoy buscando. Obtener coordenadas para las esquinas de la imagen no es necesariamente fácil ni obvio, y dado que los mapas integrados se unen automágicamente, tal vez estudiar la API de javascript podría dar una idea. ¡Gracias por tus consejos y por tu bonito fragmento de código! – heltonbiker

5

Ésta es Daniel Roseman's answer para las personas que utilizan Python 3. x:

Python 3.x código:

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() 
3

Un método compatible con más concisa Python 2.x es

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") 
1

La forma más sencilla de tener la imagen del mapa estático Google capturado/guardados (como 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() 
+0

No funciona: / – rvcristiand

5

Sobre la base de la excelente respuesta de heltonbiker con los cambios de BenElgar, por debajo de un cierto código actualizado para Python 3 y la adición de acceso clave de API, la esperanza de su utilidad para alguien:

""" 
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() 
Cuestiones relacionadas