Tkinter结合Basemap创建带国家按钮的交互式地图遇问题求助
问题描述
用户尝试使用tkinter与mpl_toolkits Basemap创建带国家按钮的交互式地图,遇到以下问题:
- 注释掉遍历
m.drawcountries()的代码段时无报错,能在Spyder IDE的Plots界面显示Basemap,但地图无法在tkinter窗口中显示 - 无法为每个国家创建对应按钮,原代码的遍历逻辑存在错误
原代码
import matplotlib.pyplot as plt from mpl_toolkits.basemap import Basemap import tkinter as tk from matplotlib.backends.backend_tkagg import FigureCanvasTk, NavigationToolbar2Tk from matplotlib.figure import Figure # Create the window root = tk.Tk() root.wm_title("Embedding in TK") # Get the canvas to draw on canvas = tk.Canvas(root, width=1000, height=800) # Create a figure fig = Figure(figsize=(5, 4), dpi=100) ax = fig.add_subplot(111) # Create a map m = Basemap(projection='mill') m.drawcoastlines(color="white") m.drawcountries(color="white") m.fillcontinents(color="grey", lake_color="black") m.drawmapboundary(fill_color="black") # a tk.DrawingArea canvas = FigureCanvasTk(fig, master=root) canvas.draw() canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) toolbar = NavigationToolbar2Tk(canvas, root) toolbar.update() canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) def _quit(): root.quit() # stops mainloop root.destroy() # this is necessary on Windows to prevent # Fatal Python Error: PyEval_RestoreThread: NULL tstate button = tk.Button(master=root, text='Quit', command=_quit) button.pack(side=tk.BOTTOM) tk.mainloop() # Create a dictionary of countries and their corresponding buttons countries_buttons = {} # Iterate through each country and create a corresponding button for country in m.drawcountries(): btn = tk.Button(root, text=country, command=(lambda country=country: print(country))) countries_buttons[country] = btn canvas.create_window(m(m.countries_info[country]['xcenter'], m.countries_info[country]['ycenter']), window=btn) # Show the map plt.show() root.mainloop()
问题分析
- 提前调用
tk.mainloop():原代码在创建基础UI后立即调用tk.mainloop(),导致后续创建国家按钮的代码完全无法执行,同时地图在tk窗口的显示逻辑因阻塞未正确完成。 - 错误遍历
m.drawcountries():m.drawcountries()返回的是matplotlib的LineCollection对象,并非国家名称列表,无法直接遍历获取国家信息。 - 不存在的
countries_info属性:Basemap默认没有countries_info属性,无法直接通过该属性获取国家中心坐标。 - 混淆tkinter Canvas与FigureCanvasTk:原代码先创建tkinter Canvas对象,随后将其覆盖为FigureCanvasTk,属于冗余操作。
- 不必要的
plt.show()调用:在tkinter嵌入matplotlib的场景中,plt.show()会创建独立的matplotlib窗口,与tkinter窗口冲突,无需调用。
修正后的代码
import matplotlib.pyplot as plt from mpl_toolkits.basemap import Basemap import tkinter as tk from matplotlib.backends.backend_tkagg import FigureCanvasTk, NavigationToolbar2Tk from matplotlib.figure import Figure import numpy as np # 创建主窗口 root = tk.Tk() root.wm_title("交互式世界地图") # 创建matplotlib Figure和Axes fig = Figure(figsize=(10, 8), dpi=100) ax = fig.add_subplot(111) # 初始化Basemap m = Basemap(projection='mill', ax=ax) m.drawcoastlines(color="white") m.fillcontinents(color="grey", lake_color="black") m.drawmapboundary(fill_color="black") # 读取国家边界shapefile(Basemap自带的国家数据) # 注意:不同环境下shapefile路径可能需要调整,默认路径在Basemap安装目录下的data文件夹 m.readshapefile('./shapefiles/cntry01', 'countries', drawbounds=True, color='white') # 创建FigureCanvasTk并嵌入tk窗口 canvas = FigureCanvasTk(fig, master=root) canvas.draw() canvas_widget = canvas.get_tk_widget() canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # 添加导航工具栏 toolbar = NavigationToolbar2Tk(canvas, root) toolbar.update() canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # 退出按钮函数 def _quit(): root.quit() root.destroy() quit_btn = tk.Button(master=root, text='退出', command=_quit) quit_btn.pack(side=tk.BOTTOM) # 创建国家按钮字典 countries_buttons = {} # 遍历读取到的国家数据,创建按钮 for info, shape in zip(m.countries_info, m.countries): country_name = info['NAME'] # 计算国家边界的中心坐标 coords = np.array(shape) lon_center = np.mean(coords[:, 0]) lat_center = np.mean(coords[:, 1]) # 转换为matplotlib轴上的坐标 x, y = m(lon_center, lat_center) # 将matplotlib坐标转换为tkinter窗口坐标 canvas_x = canvas_widget.winfo_x() + ax.transData.transform((x, y))[0] canvas_y = canvas_widget.winfo_y() + ax.transData.transform((x, y))[1] # 创建按钮 btn = tk.Button(root, text=country_name, command=lambda name=country_name: print(f"点击了国家:{name}")) countries_buttons[country_name] = btn # 将按钮放置在对应位置 btn.place(x=canvas_x, y=canvas_y, anchor='center') # 启动tk主循环 root.mainloop()
关键说明
- 读取国家shapefile:使用
m.readshapefile()读取Basemap自带的国家边界数据,获取每个国家的名称和边界坐标,进而计算中心位置。注意shapefile路径可能需要根据环境调整,默认路径通常是Basemap安装目录/data/cntry01.shp,若找不到可手动下载国家边界shp文件。 - 调整主循环位置:将
root.mainloop()移到所有初始化代码之后,确保所有UI元素(包括国家按钮)创建完成后再启动事件循环。 - 坐标转换:先计算国家边界的中心经纬度,通过Basemap转换为matplotlib轴坐标,再转换为tkinter窗口绝对坐标,确保按钮准确显示在国家中心位置。
- 移除冗余代码:删除原代码中多余的tk.Canvas创建和
plt.show()调用,避免冲突和冗余。
内容的提问来源于stack exchange,提问作者Scieoner
相关产品推荐
相关产品推荐

