Рассчитать значения пикселей по координатам широты / долготы (используя базовую карту matplotlib) - PullRequest
6 голосов
/ 24 января 2012

Мне нужно преобразовать координаты карты в пиксели (чтобы сделать интерактивную карту в формате html).

Вот пример карты (сделанный с использованием пакета Basemap из matplotlib). Я наложил на него несколько меток и попытался вычислить середины меток в пикселях:

#!/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)))

Эти рассчитанные точки находятся в приблизительно правильном относительном отношении друг к другу, но не совпадают с истинными точками. Следующий фрагмент кода должен поставить красную точку в середине каждой метки:

# 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

  • Красные точки должны быть в середине меток.

Я предполагаю, что ошибка возникает, когда я извлекаю координаты текстовых полей (на шаге № 2). Любая помощь высоко ценится.

Примечания

1 Ответ

4 голосов
/ 28 января 2012

Происходят две вещи, которые приводят к тому, что ваши позиции в пикселях отключаются.

  1. Точка на дюйм, используемая для расчета положения текста, отличается от той, которая использовалась для сохранения рисунка.

  2. Когда вы используете опцию bbox_inches в вызове savefig, это устраняет много пробелов. Вы не учитываете это, когда рисуете свои круги с помощью PIL (или проверяете, где кто-то нажал. Также вы добавляете заполнение в этот вызов savefig, который вам, возможно, придется учитывать, если он очень большой (как я покажите в моем примере ниже.) Вероятно, это не будет иметь значения, если вы все еще используете 0.01.

Чтобы исправить эту первую проблему, просто заставьте число и вызов savefig использовать один и тот же DPI.

Чтобы устранить вторую проблему, задокументируйте (0,0) положение (Оси в единицах) осей в пикселях и соответственно измените положение текста.

Вот немного измененная версия вашего кода:

#!/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]

Шаг № 2 не изменяется

Шаг № 3 учитывает происхождение

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

В результате:

enter image description here

Обратите внимание, что я использовал преувеличенное значение PADDING, чтобы проверить, что все по-прежнему работает, а значение 0,01 даст исходную цифру.

...