Calculating pixel values ​​from latitude / longitude coordinates (using the matplotlib database)

I need to convert map coordinates to pixels (to make a clickable map in html).

Here is an example map (made using the Basemap package from matplotlib). I put several labels on it and tried 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 approximately correct relative relation to each other, but do not coincide with the true points. The following code snippet should place a red dot in the middle 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") 

sample output

  • Red dots should be in the middle of the labels.

I assume that the error occurs when I retrieve the coordinates of the text fields (in step # 2). Any help is greatly appreciated.

Notes

+6
source share
1 answer

Two things happened that caused your pixel positions to be disabled.

  • The items used to calculate the text position are different from those used to save the shape.

  • When you use the bbox_inches parameter in a savefig call, it removes many spaces. You do not take this into account when drawing circles using PIL (or checking where someone clicked). You also add an add-on to this savefig call, which you might need to take into account if it is very large (since I am in my example below). It probably doesn't matter if you are still using 0.01.

To fix this first problem, just make the shape and savefig call use the same DPI.

To fix the second problem, write down the (0,0) position (s) of the axes in pixels and shift your text positions accordingly.

Here's a slightly modified version of your code:

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

Step number 2 does not change

Step 3 takes into account the origin

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

It leads to:

enter image description here

Please note that I used the exaggerated PADDING value to verify that everything is still working, and a value of 0.01 will lead to the original shape.

+4
source

Source: https://habr.com/ru/post/906771/


All Articles