Polygon containment test in matplotlib artist

情到浓时终转凉″ 提交于 2020-01-01 06:57:47

问题


I have the following code, gathered initially from here., which uses matplotlib, shapely, cartopy to draw a world map.

When a click is made, I need to determine on which country it was made. I am able to add a pick_event callback to the canvas, however, it is called on every artist.(cartopy.mpl.feature_artist.FeatureArtist, which corresponds to a country).

Given an artist and a mouse event with x, y coordinates, how can I determine containment?

I've tried artist.get_clip_box().contains, but it is not really a polygon, rather a plain rectangle.

The default containment test for the FeatureArists is None, so I had to add my own containment test.

How can I correctly check for the containment of the mouse event point, inside the 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:


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



来源:https://stackoverflow.com/questions/23399704/polygon-containment-test-in-matplotlib-artist

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