【问题标题】:Calculate pixel values from latitude/longitude coordinates (using matplotlib Basemap)从纬度/经度坐标计算像素值(使用 matplotlib 底图)
【发布时间】:2018-02-11 06:23:32
【问题描述】:

我需要将地图坐标转换为像素(以便在 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)))

这些计算的点彼此之间的相对关系大致正确,但与真实点不重合。下面的代码 sn -p 应该在每个标签的中点放一个红点:

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

  • 红点应位于标签中间。

我猜错误出现在我提取文本框坐标的位置(在步骤 #2 中)。非常感谢任何帮助。

备注

【问题讨论】:

标签: python matplotlib geospatial


【解决方案1】:

有两件事会导致您的像素位置偏离。

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

  2. 当您在savefig 调用中使用bbox_inches 选项时,它会消除大量空白。当您使用 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")

这会导致:

请注意,我使用了一个夸张的 PADDING 值来测试一切是否仍然有效,而 0.01 的值将产生您的原始数字。

【讨论】:

  • 太棒了!非常感谢您提供完整、清晰且经过测试的答案。
猜你喜欢
  • 1970-01-01
  • 2013-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-31
  • 1970-01-01
相关资源
最近更新 更多