如何在Geopandas地图指定位置添加同CRS的柱状图
问题
我用欧盟统计局(Eurostat)的NUTS2021官方多边形数据(GeoJSON格式,Polygon (RG)几何类型,20M比例尺,EPSG 3857坐标系)和Geopandas制作欧洲国家地图,想要在特定位置(多数为国家质心,部分国家需多个位置)添加柱状图。这些位置坐标来自欧盟统计局NUTS2021点数据(GeoJSON格式,Points (LB)几何类型,EPSG 3857坐标系)或其他数据源(如Google公开数据)。
目前我知道用fig.add_axes(rect)添加柱状图,其中rect是[x0, y0, width, height]格式的figure坐标,但我希望用预定义的地理坐标(比如德国的NUTS点坐标(1155338.417 6634297.661),或者CSV里的经纬度(10.451526, 51.165691))来定位柱状图,而非手动计算figure坐标,该怎么实现?
已尝试方案
我试过用lon_eu_min, lon_eu_max, lat_eu_min, lat_eu_max对坐标做归一化,但没成功,因为柱状图是基于figure坐标而非地图轴坐标定位的。手动设置figure坐标可以实现,示例代码如下:
import cartopy.crs as ccrs import geopandas as gpd import matplotlib.pyplot as plt # 导入并筛选国家多边形数据 nuts_path = '.\\NUTS_RG_20M_2021_3857.geojson' gdf = gpd.read_file(nuts_path) gdf = gdf[gdf['LEVL_CODE'] == 0] gdf = gdf[['CNTR_CODE', 'geometry']] gdf = gdf.rename(columns={'CNTR_CODE': 'region'}) gdf = gpd.GeoDataFrame(gdf, geometry='geometry', crs=3857) # 导入并筛选国家点数据 nuts_points_path = '.\\NUTS_LB_2021_3857.geojson' gdf_points = gpd.read_file(nuts_points_path) gdf_points = gdf_points.query("LEVL_CODE == 0") gdf_points = gdf_points[['CNTR_CODE', 'geometry']] gdf_points = gdf_points.rename(columns={'CNTR_CODE': 'region'}) # 绘图 fig = plt.figure(figsize=(9, 10)) ax = plt.axes(projection=ccrs.epsg(3857)) # 设置地图范围 lat_eu_min = 35 lat_eu_max = 71 lon_eu_min = -12 lon_eu_max = 34 ax.set_extent([lon_eu_min, lon_eu_max, lat_eu_min, lat_eu_max]) # 绘制国家边界 gdf.plot( ax=ax, edgecolor='black', facecolor='lightgrey', ) # 手动设置figure坐标定位柱状图 longitude, latitude = 0.46, 0.33 # 添加柱状图 bar_ax = fig.add_axes([longitude, latitude, 0.05, 0.05]) bar_ax.bar([1, 2, 3], [1, 2, 3], color=['C1', 'C2', 'C3']) bar_ax.set_axis_off() plt.tight_layout() plt.show()
手动定位的预期效果:
解决方法
核心思路是将地理坐标转换为figure坐标,步骤如下:
- 把地理坐标转换为地图轴(ax)的相对坐标(范围[0,1])
- 将该相对坐标直接作为
fig.add_axes()的定位参数
具体实现
1. 坐标转换函数
定义函数完成地理坐标到figure坐标的转换,支持不同CRS的输入坐标:
def geo_to_figure_coords(geo_x, geo_y, ax, input_crs): # 将输入CRS的地理坐标转换为当前地图投影的坐标 projected_point = ax.projection.transform_point(geo_x, geo_y, input_crs) # 将投影坐标转换为axes相对坐标([0,1]范围,对应figure比例) fig_coord = ax.transAxes.inverted().transform(ax.transData.transform(projected_point)) return fig_coord
2. 完整示例代码
以德国的NUTS点坐标(EPSG 3857)和柏林经纬度(WGS84)为例,自动定位柱状图:
import cartopy.crs as ccrs import geopandas as gpd import matplotlib.pyplot as plt # 坐标转换函数 def geo_to_figure_coords(geo_x, geo_y, ax, input_crs): projected_point = ax.projection.transform_point(geo_x, geo_y, input_crs) fig_coord = ax.transAxes.inverted().transform(ax.transData.transform(projected_point)) return fig_coord # 导入并处理多边形数据 nuts_path = '.\\NUTS_RG_20M_2021_3857.geojson' gdf = gpd.read_file(nuts_path) gdf = gdf[gdf['LEVL_CODE'] == 0][['CNTR_CODE', 'geometry']].rename(columns={'CNTR_CODE': 'region'}) gdf = gpd.GeoDataFrame(gdf, geometry='geometry', crs=3857) # 导入并处理点数据 nuts_points_path = '.\\NUTS_LB_2021_3857.geojson' gdf_points = gpd.read_file(nuts_points_path) gdf_points = gdf_points.query("LEVL_CODE == 0")[['CNTR_CODE', 'geometry']].rename(columns={'CNTR_CODE': 'region'}) # 创建绘图对象 fig = plt.figure(figsize=(9, 10)) ax = plt.axes(projection=ccrs.epsg(3857)) ax.set_extent([-12, 34, 35, 71]) # 绘制国家 gdf.plot(ax=ax, edgecolor='black', facecolor='lightgrey') # 示例1:用EPSG 3857坐标定位(德国NUTS质心) de_x, de_y = 1155338.417, 6634297.661 fig_x, fig_y = geo_to_figure_coords(de_x, de_y, ax, ccrs.epsg(3857)) bar_ax1 = fig.add_axes([fig_x, fig_y, 0.05, 0.05]) bar_ax1.bar([1,2,3], [2,3,1], color=['#FF9999', '#99FF99', '#9999FF']) bar_ax1.set_axis_off() # 示例2:用WGS84经纬度定位(柏林) berlin_lon, berlin_lat = 13.4050, 52.5200 fig_x2, fig_y2 = geo_to_figure_coords(berlin_lon, berlin_lat, ax, ccrs.PlateCarree()) bar_ax2 = fig.add_axes([fig_x2, fig_y2, 0.04, 0.04]) bar_ax2.bar([1,2], [4,2], color=['#FFCC00', '#CC00FF']) bar_ax2.set_axis_off() # 示例3:从NUTS点数据中提取坐标定位 de_point = gdf_points[gdf_points['region'] == 'DE'].iloc[0] fig_x3, fig_y3 = geo_to_figure_coords(de_point.geometry.x, de_point.geometry.y, ax, ccrs.epsg(3857)) # 可在此添加第三个柱状图 plt.tight_layout() plt.show()
关键说明
ax.projection.transform_point():完成不同CRS之间的坐标转换,确保输入坐标和地图投影匹配ax.transData.transform():将地图数据坐标转为像素坐标ax.transAxes.inverted().transform():将像素坐标转为axes相对坐标,直接对应figure的比例位置- 柱状图的宽高参数(如0.05)是figure尺寸的比例,可根据需求调整
内容的提问来源于stack exchange,提问作者Sacrademius
相关产品推荐
相关产品推荐

