从经纬度坐标计算像素值(使用matplotlib Basemap)
我需要把地图上的坐标转换成像素(这样才能在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")
- 红点应该在标签的正中间。
我猜错误可能出现在我提取文本框坐标的步骤(第2步)。非常感谢任何帮助。
备注
- 也许解决方案可以参考这个回答?
1 个回答
5
有两个原因导致你的像素位置不对。
计算文本位置时使用的dpi(每英寸点数)和保存图形时使用的dpi不一样。
当你在调用
savefig
时使用了bbox_inches
选项,这会去掉很多空白区域。而你在用画圆圈时(或者检查别人点击的位置)没有考虑到这一点。此外,在这个 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的值会得到你原来的图形。