用Cartopy库画error map以及一些legend的进阶设置

1 引言

网上关于Python第三方库Cartopy的教程不是很多,而且多少有些重复,今天笔者就提供一个Cartopy的绘图实战入门教程,希望对大家有所启发;同时,因为Cartopy绘图时会与matplotlib耦合,这里我提供了一种进阶的legend (图例) 设置方法,更为详细的操作和介绍可以参阅相关文档:

Cartopy官方文档:

https://scitools.org.uk/cartopy/docs/latest/

legend guide:

https://matplotlib.org/3.2.1/tutorials/intermediate/legend_guide.html#proxy-legend-handles

2 Cartopy

简言之就是Basemap的替代品,主要用于将各种空间数据绘制在地图上。本文绘制地图中的一些主要的空间数据(River, Roads, state等)来源于Natural Earth 数据中心。

3 实验背景&数据

这是Illinois State Will County地下水模型的一部分,之前已经通过flopy耦合modflow模拟了该地区的地下水水头变化趋势。现在引入一组实际观测数据作为校核数据,来绘制该模型区域的水头误差图error map (即计算各个坐标下实际水头和模拟水头的差值)
水头差 (error):水头模拟值存储在modflow模拟产生的hds文件中,通过flopy读取。水头实测值通过csv文件读入,计算error后存入一个DataFrame (pumping_ob)中。DataFrame中另外两个重要的参数lambx和lamby显示测量点的Lambert坐标
在这里插入图片描述

4 Code

1
2
3
4
5
6
7
8
9
import matplotlib as mp
import pandas as pd
import pyproj # change between WGS84 and Illimap coordinates
import matplotlib.pyplot as plt
import numpy as np
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cf
from pykrige.uk import UniversalKriging

pyproj库实现Lambert坐标和经纬度坐标的转换,pykrige实现克里金空间插值
需要注意,如果在Colab中运行代码,要首先下载pyproj,cartopy和pykrige:

1
2
3
!pip install pyproj
!pip install pykrige
!apt-get -qq install python-cartopy python3-cartopy

首先进行坐标转换,创建两列lat,long存储转换后的经纬度坐标。pyproj.transform 能实现不同坐标系的参数转换

1
2
3
4
# convert position of each observed data points from (lambx,lamby) to (long,lat)
for ind in pumping_ob.index:
  pumping_ob.loc[ind,'lat'], pumping_ob.loc[ind,'long'] = pyproj.transform(illimap,wgs84,pumping_ob.loc[ind,'lambx']*0.3048,pumping_ob.loc[ind,'lamby']*0.3048)
# print(pumping_ob)

Kriging法是空间建模时常见的插值方法,pykrige.uk.UniversalKriging 通过给定每个点的longitude,latitude,每个点的高度以及采用的方差模型来进行空间插值;
用经度的上下界 (xpoints) 和纬度的上下界 (ypoints) 分别生成模型区域网格的横纵坐标范围,以两个ndarray的形式展现;np.meshgrid根据网格点范围生成网格模型;

execute(style, xpoints, ypoints, mask=None, backend=‘vectorized’, specified_drift_arrays=None)

UK.execute返回插值结果和方差值;用NaN填充插值结果中可能出现的缺失值。

1
2
3
4
5
6
7
8
9
10
11
12
# conduct the Universal Kriging
UK = UniversalKriging(pumping_ob['long'], pumping_ob['lat'], pumping_ob['error'],
                      variogram_model='spherical', nlags=6)

# create xpoints and ypoints in space, with 0.01 spacing
xpoints = np.arange(sw_long, ne_long+0.01, 0.01)
ypoints = np.arange(sw_lat, ne_lat+0.01, 0.01)
# create a meshgrid with xpoints and ypoints, to be used later in the code
Xm, Ym = np.meshgrid(xpoints, ypoints)
# calculate the interpolated grid and fill values.
z, var = UK.execute('grid', xpoints, ypoints)
z = z.filled(fill_value=None)

关于以上步骤也可参阅如下英文解释:
在这里插入图片描述
接下来到了重头戏,我们定义一个函数error_map用来进行绘图操作。采用PlateCarree (矩形) 投影系统,然后从Natural Earth 网站获取各种空间数据,各种特征数据的定义格式如下:

cartopy.feature.NaturalEarthFeature(category, name, scale, **kwargs)

这里我引入了行政区划,河流和主要街道,再通过ax.add_feature() 将特征数据添加到图上
plt.contour 用于绘制等高线,plt.contourf 用于在等高线之间填充颜色,cmap设置配色方案,plt.clabel 用于设置各等高线处的数值标签。关于这三个的用法可以参考莫烦老师的相关视频。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def error_map(Xm, Ym, z, df, title):
    """
        this function uses the cartopy package to draw error map given the DataFrame
    """
    fig = plt.figure(figsize=(16,12))
    # set projection kind
    ax = plt.axes(projection=ccrs.PlateCarree())
    # set features for different obejects
    states_provinces = cf.NaturalEarthFeature(
      category='cultural', # features are categorized as cultural or physical
      name='admin_1_states_provinces', # name of shapefile
      scale='10m', # scale of features
      facecolor='none')
    largerivers = cf.NaturalEarthFeature(
      category='physical',
      name='rivers_lake_centerlines',
      scale='110m', # major rivers
      facecolor='none')
    smallrivers = cf.NaturalEarthFeature(
      category='physical',
      name='rivers_lake_centerlines_scale_rank',
      scale='10m', # smaller rivers
      facecolor='none')
    smallestrivers = cf.NaturalEarthFeature(
      category='physical',
      name='rivers_north_america',
      scale='10m', # smallest rivers
      facecolor='none')  
    popplaces = cf.NaturalEarthFeature(
      category='cultural',
      name='urban_areas', # plots municipal boundaries
      scale='10m',
      facecolor='plum')
    majorroads = cf.NaturalEarthFeature(
      category='cultural',
      name='roads',
      scale='10m',
      facecolor='none')
   
    # add defined features into axes
    ax.add_feature(states_provinces, edgecolor='slategray', linewidth=2.0,linestyle=':',zorder=4)
    ax.add_feature(popplaces,color='darkgray',alpha=0.2,linewidth=0.1, zorder=1)
    ax.add_feature(largerivers,edgecolor='aqua',linewidth=6.0)
    ax.add_feature(smallrivers,edgecolor='aqua',linewidth=6.0, zorder=2)
    ax.add_feature(smallestrivers,edgecolor='aqua',linewidth=6.0)
    ax.add_feature(majorroads,edgecolor='black',linewidth=1.0)
   
    # create contours from the interpolation and fill the interval according to given colorbar
    cset = plt.contour(Xm, Ym, z, np.arange(-40, 60, 10), colors='blue')
    C = plt.contourf(Xm, Ym, z, np.arange(-40, 60, 10), alpha=.75,
                         cmap=plt.cm.coolwarm_r)  # reverse of colorbar 'jet'
   
    plt.colorbar(C)
    # contour labels
    plt.clabel(cset, inline=1, fontsize=10, fmt='%1.0f')
    # show surface points on the map
    points = plt.scatter(df['long'],df['lat'],marker=".",color="black",label='surface points')

这样就基本上生成了error map。但是这里有一个小问题,由于ax.add_feature() 不同于一般的plt.plot(),是无法直接通过设置label关键词和添加plt.legend()来实现图例的添加的。所以这里需要给legend添加句柄handle。主要有两种方法来实现这一效果,我这里都采用了:

1
2
3
4
5
6
7
8
9
 # add legend to the map
    rvr_patch = mp.patches.Patch(color='aqua',label='River')
    road_line = mp.lines.Line2D([],[],color='black',label='Major roads')

    plt.title(title,fontsize=16)
    plt.xlim(sw_long,ne_long)
    plt.ylim(sw_lat,ne_lat)
    plt.legend(bbox_to_anchor=(1.3,1),handles=[points,rvr_patch,road_line])
    plt.show()

mp.patches.Patch 可以通过设置填充色和边框颜色来形成一个可以传入handles中的2D patch,我这里用来设置河流图例,因为前面画河流时设置的线宽比较宽。
mp.lines.Line2D 通过传入两个空列表来创建一个没有具体方位的Line2D实例,因为前面设置道路的线宽比较小,所以我这里采样此法添加道路图例。

5 出图

在这里插入图片描述
出图效果还是很不错的,大部分误差值能控制在30ft的范围内,当然在北方有一小部分误差超过了40ft,这说明前期模拟效果还不是最佳,这也是该地下水模型需要继续完善校核的地方。