matplotlib artist中的多边形包含性测试
我有一段代码,最初是从这里找到的,它使用了matplotlib、shapely和cartopy来绘制世界地图。
当我点击地图时,我需要知道我点击的是哪个国家。我可以在画布上添加一个pick_event
的回调函数,但这个函数会在每个艺术家(也就是每个国家)上被调用。
给定一个艺术家和一个鼠标事件的x、y坐标,我该如何判断这个点是否在艺术家里面呢?
我试过用artist.get_clip_box().contains
,但这并不是真正的多边形,而只是一个普通的矩形。
对于FeatureArtist
,默认的包含测试是None
,所以我不得不添加我自己的包含测试。
我该如何正确检查鼠标事件的点是否在FeatureArtist里面呢?
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.io.shapereader as shpreader
import itertools, pdb, subprocess, time, traceback
from itertools import *
import numpy as np
from pydoc import help as h
shapename = 'admin_0_countries'
countries_shp = shpreader.natural_earth(resolution='110m',
category='cultural', name=shapename)
earth_colors = np.array([(199, 233, 192),
(161, 217, 155),
(116, 196, 118),
(65, 171, 93),
(35, 139, 69),
]) / 255.
earth_colors = itertools.cycle(earth_colors)
ax = plt.axes(projection=ccrs.PlateCarree())
def contains_test ( artist, ev ):
print "contain test called"
#this containmeint test is always true, because it is a large rectangle, not a polygon
#how to define correct containment test
print "click contained in %s?: %s" % (artist.countryname, artist.get_clip_box().contains(ev.x, ev.y))
return True, {}
for country in shpreader.Reader(countries_shp).records():
# print country.attributes['name_long'], earth_colors.next()
art = ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=earth_colors.next(),
label=country.attributes['name_long'])
art.countryname = country.attributes["name_long"]
art.set_picker(True)
art.set_contains(contains_test)
def pickit ( ev ):
print "pickit called"
print ev.artist.countryname
def onpick ( event ):
print "pick event fired"
ax.figure.canvas.mpl_connect("pick_event", onpick)
def onclick(event):
print 'button=%s, x=%s, y=%s, xdata=%s, ydata=%s'%(event.button, event.x, event.y, event.xdata, event.ydata)
ax.figure.canvas.mpl_connect('button_press_event', onclick)
plt.show()
1 个回答
2
这是个好问题。不过,很遗憾,FeatureArtist并不是PathCollection的子类,虽然它本来应该是的,而是直接继承自Artist。这就意味着,正如你已经发现的那样,艺术家(artist)上并没有定义包含测试,实际上,在目前的状态下,想要绕过这个问题并不容易。
不过,我可能不会用matplotlib的包含功能来处理这个问题;考虑到我们有Shapely几何图形,而包含测试正是这个工具的核心功能,我会记录下用来创建艺术家的Shapely几何图形,然后对其进行检查。接着,我会用一个类似下面的函数来连接matplotlib的通用事件处理:
def onclick(event):
if event.inaxes and isinstance(event.inaxes, cartopy.mpl.geoaxes.GeoAxes):
ax = event.inaxes
target = ccrs.PlateCarree()
lon, lat = target.transform_point(event.xdata, event.ydata,
ax.projection)
point = sgeom.Point(lon, lat)
for country, (geom, artist) in country_to_geom_and_artist.items():
if geom.contains(point):
print 'Clicked on {}'.format(country)
break
这个函数的难点在于获取经纬度的x和y坐标,但之后就简单多了,只需要创建一个Shapely的点,并检查它是否在每个国家的几何图形内。
完整的代码大概是这样的:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import cartopy.io.shapereader as shpreader
import cartopy.mpl.geoaxes
import itertools
import numpy as np
import shapely.geometry as sgeom
shapename = 'admin_0_countries'
countries_shp = shpreader.natural_earth(resolution='110m',
category='cultural', name=shapename)
earth_colors = np.array([(199, 233, 192), (161, 217, 155),
(116, 196, 118), (65, 171, 93),
(35, 139, 69)]) / 255.
earth_colors = itertools.cycle(earth_colors)
ax = plt.axes(projection=ccrs.Robinson())
# Store a mapping of {country name: (shapely_geom, cartopy_feature)}
country_to_geom_and_artist = {}
for country in shpreader.Reader(countries_shp).records():
artist = ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=earth_colors.next(),
label=repr(country.attributes['name_long']))
country_to_geom_and_artist[country.attributes['name_long']] = (country.geometry, artist)
def onclick(event):
if event.inaxes and isinstance(event.inaxes, cartopy.mpl.geoaxes.GeoAxes):
ax = event.inaxes
target = ccrs.PlateCarree()
lon, lat = target.transform_point(event.xdata, event.ydata,
ax.projection)
point = sgeom.Point(lon, lat)
for country, (geom, artist) in country_to_geom_and_artist.items():
if geom.contains(point):
print 'Clicked on {}'.format(country)
break
ax.figure.canvas.mpl_connect('button_press_event', onclick)
plt.show()
如果包含测试的数量比这个形状文件中的要多得多,我还会考虑对每个国家的几何图形进行“预处理”,这样可以大大提高性能。
希望这对你有帮助。