从纬度/经度坐标计算像素值(使用matplotlib底图)

时间:2012-01-24 16:22:05

标签: python matplotlib geospatial

我需要将地图坐标转换为像素(为了在html中制作可点击的地图)。

这是一个示例地图(使用matplotlib的Basemap包制作)。我在其上放了一些标签,并尝试以像素计算标签的中点:

#!/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 个答案:

答案 0 :(得分:4)

发生了两件事情导致您的像素位置关闭。

  1. 用于计算文本位置的dpi与用于保存图形的dpi不同。

  2. bbox_inches调用中使用savefig选项时,会消除大量空白区域。当您使用PIL绘制圈子时(或检查某人点击的位置),您不会考虑这一点。此外,您还可以在此savefig来电中添加一个填充,如果它非常大(正如我在下面的例子中所示)。如果你仍然使用0.01,可能无关紧要。

  3. 要解决此第一个问题,只需强制数字和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会产生原始数字。