I need to convert map coordinates into pixels (in order to make a clickable map in html).
Here is a sample map (made using the Basemap package from matplotlib). I have put some labels on it and attempted to calculate the midpoints of the labels in pixels:
#!/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)))
These calculated points are in the approximately correct relative relation to each other, but do not coincide with the true points. The following code snippet should put a red dot on the midpoint of each label:
# 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")

- Red dots should be in the middle of the labels.
I guess that the error comes in where I extract the coordinates of the text boxes (in Step #2). Any help much appreciated.
Notes
- Perhaps the solution is something along the lines of this answer?
Two things are happening to cause your pixel positions to be off.
The dpi used to calculated the text position is different from that used to save the figure.
When you use the
bbox_inchesoption in thesavefigcall, it eliminates a lot of white space. You don’t take this into account when you are drawing your circles withPIL(or checking where someone clicked. Also you add a padding in thissavefigcall that you may need to account for if it’s very large (as I show in my example below). Probably it will not matter if you still use 0.01.To fix this first issue, just force the figure and the
savefigcall to use the same DPI.To fix the second issue, document the (0,0) position (Axes units) of the axes in pixels, and shift your text positions accordingly.
Here’s a slightly modified version of your code:
Step #2 is unchanged
Step #3 takes account of the origin
This results in:
Notice that I used an exaggerated
PADDINGvalue to test that everything still works, and a value of 0.01 would produce your original figure.