Polygon containment test in matplotlib artist

我们两清 提交于 2019-12-03 21:43:33

Good question. Sadly, it looks like the FeatureArtist isn't a subclass of PathCollection, as it technically should be, but it simply inherits from Artist. This means that, as you've already spotted, the containment test isn't defined on the artist, and in truth, it isn't particularly easy to work around in its current state.

That said, I probably wouldn't have approached this using the matplotlib containment functionality; given that we have Shapely geometries, and that containment is the bread and butter of such a tool, I'd keep track of the shapely geometry that went into creating the artist, and interrogate that. I'd then simply hook into matplotlib's generic event handling with a function along the lines of:

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

The difficulty in this function was getting hold of the x and y coordinates in terms of latitudes and longitudes, but after that, it is a simple case of creating a shapely Point and checking containment on each of the countries' geometries.

The full code then looks something like:

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()

If the number of containment tests increase much more than that within this shape file, I'd also be looking at "preparing" each country's geometry, for a pretty major performance boost.

HTH

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!