How to show shapefile label in python basemap lege

2019-01-11 15:39发布

问题:

The python Basemap has two contents plotted in it: A multiline shapefile (IL_State_ln) and a scatterplot of some random points within the basemap extent. My interest is in generating a legend that gives information about both shapefile and the scatter points. For now, I am only able to include the points in the legend and not the shapefile.

Checking the Basemap API documentation does not provide any information, as the function readshapefile() does not seem to have any label argument.

Could you please help me include the shapefile indicator in the legend like in the ArcGIS maps?

Here is my code:

import numpy as np    
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap

fname = "DATA/GIS/IL_State_ln"

m = Basemap(llcrnrlon=-92.,llcrnrlat=36.8,urcrnrlon=-86.5,urcrnrlat=43.,
             resolution='i', projection='tmerc', lat_0 = 36.5, lon_0 = -91.8)

m.readshapefile(fname, 'mygeom')

x,y = m([-90., -91.2, -88.], [38., 37.7, 42.])
m.scatter(x,y, marker='o', label="Points")

plt.legend(loc=3)
plt.show()

I'm using Python 3.5, matplotlib 2.0 and basemap 1.0.8.

回答1:

The idea to create a legend entry would be to draw the shapes as polygons, which can then be added to the legend.
Therfore we would first deactivate drawbounds, m.readshapefile(fn, 'shf', drawbounds = False). We can then create a matplotlib.patches.Polygon from the shapefile and add it to the axes, plt.gca().add_artist(polygon).

The legend can then be updated using this polygon

handles, labels = plt.gca().get_legend_handles_labels()
handles.extend([polygon])  
labels.extend(["Name of the shape"])                     
plt.legend(handles=handles, labels=labels)

Here is now some code in action, which produces the following images. It uses the ne_10m_admin_0_countries file.

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np

m = Basemap(llcrnrlon=-10,llcrnrlat=35,urcrnrlon=35,urcrnrlat=60.,
             resolution='i', projection='tmerc', lat_0 = 48.9, lon_0 = 15.3)

m.drawcoastlines()
m.drawcountries(zorder=0, color=(.9,.9,.9), linewidth=1)

fn = r"ne_10m_admin_0_countries\ne_10m_admin_0_countries"
m.readshapefile(fn, 'shf', drawbounds = False)

#Madrid
x,y = m([-3.703889],[40.4125])
m.plot(x,y, marker="o", color="blue", label="Madrid", ls="")

# some countries
countries = ['Switzerland', 'Ireland', "Belgium"]
colors= {'Switzerland':"red", 'Ireland':"orange", 'Belgium' : "purple"}
shapes = {}
for info, shape in zip(m.shf_info, m.shf):
    if info['NAME'] in countries:
        p= Polygon(np.array(shape), True, facecolor= colors[info['NAME']], 
                   edgecolor='none', alpha=0.7, zorder=2)
        shapes.update({info['NAME'] : p})

for country in countries:
    plt.gca().add_artist(shapes[country]) 


# create legend, by first getting the already present handles, labels
handles, labels = plt.gca().get_legend_handles_labels()
# and then adding the new ones
handles.extend([shapes[c] for c in countries])  
labels.extend(countries)                     
plt.legend(handles=handles, labels=labels, framealpha=1.)

plt.show()

Now because we already have a polygon with the shape, why not make the legend a bit more fancy, by directly plotting the shape into the legend. This can be done as follows.

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np

m = Basemap(llcrnrlon=-10,llcrnrlat=35,urcrnrlon=35,urcrnrlat=60.,
             resolution='i', projection='tmerc', lat_0 = 48.9, lon_0 = 15.3)

m.drawcoastlines()

fn = r"ne_10m_admin_0_countries\ne_10m_admin_0_countries"
m.readshapefile(fn, 'shf', drawbounds = False)

#Madrid
x,y = m([-3.703889],[40.4125])
m.plot(x,y, marker="o", color="blue", label="Madrid", ls="")

countries = ['Switzerland', 'Ireland', "Belgium"]
colors= {'Switzerland':"red", 'Ireland':"orange", 'Belgium' : "purple"}
shapes = {}
for info, shape in zip(m.shf_info, m.shf):
    if info['NAME'] in countries:
        p= Polygon(np.array(shape), True, facecolor= colors[info['NAME']], 
                   edgecolor='none', alpha=0.7, zorder=2)
        shapes.update({info['NAME'] : p})

for country in countries:
    plt.gca().add_artist(shapes[country]) 


class PolygonN(object):
    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
        x0, y0 = handlebox.xdescent, handlebox.ydescent
        width, height = handlebox.width, handlebox.height
        aspect= height/float(width)
        verts = orig_handle.get_xy()
        minx, miny = verts[:,0].min(), verts[:,1].min()
        maxx, maxy = verts[:,0].max(), verts[:,1].max()
        aspect= (maxy-miny)/float((maxx-minx))
        nvx = (verts[:,0]-minx)*float(height)/aspect/(maxx-minx)-x0
        nvy = (verts[:,1]-miny)*float(height)/(maxy-miny)-y0

        p = Polygon(np.c_[nvx, nvy])
        p.update_from(orig_handle)
        p.set_transform(handlebox.get_transform())

        handlebox.add_artist(p)
        return p

handles, labels = plt.gca().get_legend_handles_labels()
handles.extend([shapes[c] for c in countries])  
labels.extend(countries)     
plt.legend(handles=handles, labels=labels, handleheight=3, handlelength=3, framealpha=1.,
           handler_map={Polygon: PolygonN()} )

plt.show()