2012-01-24 18 views
6

Necesito convertir las coordenadas del mapa en píxeles (para hacer un mapa cliqueable en html).Calcular valores de píxeles a partir de las coordenadas de latitud/longitud (usando el mapa base matplotlib)

Aquí hay un mapa de muestra (hecho usando el paquete Basemap de matplotlib). He puesto algunas etiquetas en él y trató de calcular los puntos medios de las etiquetas en píxeles:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

## Step 0: some points to plot 
names = [u"Reykjavík", u"Höfn", u"Húsavík"] 
lats = [64.133333, 64.25, 66.05] 
lons = [-21.933333, -15.216667, -17.316667] 

## Step 1: draw a map using matplotlib/Basemap 
from mpl_toolkits.basemap import Basemap 
import matplotlib.pyplot as plt 

M = Basemap(projection='merc',resolution='c', 
      llcrnrlat=63,urcrnrlat=67, 
      llcrnrlon=-24,urcrnrlon=-13) 

x, y = M(lons, lats) # transform coordinates according to projection 
boxes = [] 
for xa, ya, name in zip(x, y, names): 
    box = plt.text(xa, ya, name, 
     bbox=dict(facecolor='white', alpha=0.5)) 
    boxes.append(box) 

M.bluemarble() # a bit fuzzy at this resolution... 
plt.savefig('test.png', bbox_inches="tight", pad_inches=0.01) 

# Step 2: get the coordinates of the textboxes in pixels and calculate the 
# midpoints 
F = plt.gcf() # get current figure 
R = F.canvas.get_renderer() 
midpoints = [] 
for box in boxes: 
    bb = box.get_window_extent(renderer=R) 
    midpoints.append((int((bb.p0[0] + bb.p1[0])/2), 
      int((bb.p0[1] + bb.p1[1])/2))) 

Estos puntos calculados están en la relación relativa aproximadamente correcta entre sí, pero no coinciden con los puntos verdaderos. El siguiente fragmento de código debe poner un punto rojo en el punto medio de cada etiqueta:

# Step 3: use PIL to draw dots on top of the labels 
from PIL import Image, ImageDraw 

im = Image.open("test.png") 
draw = ImageDraw.Draw(im) 
for x, y in midpoints: 
    y = im.size[1] - y # PIL counts rows from top not bottom 
    draw.ellipse((x-5, y-5, x+5, y+5), fill="#ff0000") 
im.save("test.png", "PNG") 

sample output

  • Los puntos rojos deben estar en el medio de las etiquetas.

Supongo que el error viene donde extraigo las coordenadas de los cuadros de texto (en el Paso # 2). Cualquier ayuda muy apreciada.

Notas

  • Tal vez la solución es algo a lo largo de las líneas de this answer?
+0

¿Podría utilizar el mapa base para dibujar los puntos rojos? Ver http://matplotlib.org/basemap/api/basemap_api.html#mpl_toolkits.basemap.Basemap.plot –

Respuesta

4

Dos cosas están sucediendo para hacer que sus posiciones de píxeles estén apagadas.

  1. El ppp utilizado para calcular la posición del texto es diferente del utilizado para guardar la figura.

  2. Cuando utiliza la opción bbox_inches en la llamada savefig, elimina una gran cantidad de espacio en blanco. No tiene esto en cuenta cuando dibuja sus círculos con PIL (o verificando dónde hizo clic alguien. También agrega un relleno en esta llamada savefig que puede necesitar para la cuenta si es muy grande (como muestro en mi ejemplo) más adelante). Probablemente no importará si todavía utiliza 0,01.

para solucionar este primer número, simplemente obligar a la figura y la llamada savefig usar el mismo DPI.

para solucionar el segundo problema , documente la posición (0,0) (unidades de ejes) de los ejes en píxeles, y cambie sus posiciones de texto en consecuencia.

Aquí es una versión ligeramente modificada de su código:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

## Step 0: some points to plot 
names = [u"Reykjavík", u"Höfn", u"Húsavík"] 
lats = [64.133333, 64.25, 66.05] 
lons = [-21.933333, -15.216667, -17.316667] 

## Step 1: draw a map using matplotlib/Basemap 
from mpl_toolkits.basemap import Basemap 
import matplotlib.pyplot as plt 

# predefined dpi 
FIGDPI=80 

# set dpi of figure, so that all calculations use this value 
plt.gcf().set_dpi(FIGDPI) 

M = Basemap(projection='merc',resolution='c', 
      llcrnrlat=63,urcrnrlat=67, 
      llcrnrlon=-24,urcrnrlon=-13) 

x, y = M(lons, lats) # transform coordinates according to projection 
boxes = [] 
for xa, ya, name in zip(x, y, names): 
    box = plt.text(xa, ya, name, 
     bbox=dict(facecolor='white', alpha=0.5)) 
    boxes.append(box) 

M.bluemarble() # a bit fuzzy at this resolution... 

# predefine padding in inches 
PADDING = 2 
# force dpi to same value you used in your calculations 
plt.savefig('test.png', bbox_inches="tight", pad_inches=PADDING,dpi=FIGDPI) 

# document shift due to loss of white space and added padding 
origin = plt.gca().transAxes.transform((0,0)) 
padding = [FIGDPI*PADDING,FIGDPI*PADDING] 

Paso # 2 es sin cambios

Paso # 3 tiene en cuenta el origen

# Step 3: use PIL to draw dots on top of the labels 
from PIL import Image, ImageDraw 

im = Image.open("test.png") 
draw = ImageDraw.Draw(im) 
for x, y in midpoints: 
    # deal with shift 
    x = x-origin[0]+padding[0] 
    y = y-origin[1]+padding[1] 
    y = im.size[1] - y # PIL counts rows from top not bottom 
    draw.ellipse((x-5, y-5, x+5, y+5), fill="#ff0000") 
im.save("test.png", "PNG") 

Esto se traduce en:

enter image description here

Tenga en cuenta que utilicé un valor PADDING exagerado para probar que todo sigue funcionando, y un valor de 0,01 produciría su figura original.

+0

¡eso es increíble! Muchas gracias por una respuesta completa, clara y probada. –

Cuestiones relacionadas