frykit


Namefrykit JSON
Version 0.7.6 PyPI version JSON
download
home_pageNone
SummaryA simple toolbox for Matplotib and Cartopy
upload_time2025-10-10 09:50:30
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # frykit

一个配合 Matplotlib 和 Cartopy 使用的地图工具箱,主要由 `shp` 和 `plot` 模块组成。

`shp` 模块的功能是:

- 读取中国行政区划数据
- 创建多边形掩膜(mask)

`plot` 模块的功能包括:

- 绘制中国行政区划数据。
- 利用行政区划做裁剪(clip)
- 快速设置地图范围和刻度
- 添加南海小图
- 添加风矢量图的图例
- 添加指北针
- 添加比例尺

特色是:

- 自带高德地图和天地图的行政区划数据
- 可同时用于 `Axes` 和 `GeoAxes`
- 对画图速度有优化
- 对裁剪出界问题有优化

暂无文档,但是每个函数都有详细的 docstring,可以在 Python 命令行中通过 `help` 函数查看,或者在 IDE 中查看。

这个包只是作者自用的小工具集,函数编写粗糙,可能存在不少 bug,还请多多交流指正。类似的更完备的包还请移步 [cnmaps](https://github.com/cnmetlab/cnmaps)、[gma](https://gma.luosgeo.com/) 或 [EOmaps](https://github.com/raphaelquast/EOmaps)。

> 有问题直接提 issue,也可以加交流 QQ 群:1017694471

## 安装

```
# 只需要 frykit 的工具函数
pip install frykit

# 需要地图数据
pip install frykit[data]

# 更新
pip install -U frykit
```

0.7.0 开始地图数据和 frykit 本体分离,完整安装和更新需要指定 `frykit[data]`,而之前的版本简单 pip 安装即可。

具体依赖为:

```
python>=3.10.0
pandas>=2.0.0
shapely>=2.0.0
cartopy>=0.22.0
```

Python 版本不满足要求时可能装上老版本的 frykit,缺少新函数或者在运行时报错。

## 更新记录

[CHANGELOG.md](CHANGELOG.md)

## 使用指南

### 读取中国行政区划

`get_cn_xxx` 系列函数能读取中国行政区划,返回 [Shapely](https://shapely.readthedocs.io/en/stable/manual.html) 多边形对象。具体来说:

- `get_cn_border`:读取国界
- `get_cn_line`:读取九段线
- `get_cn_province`:读取省界。默认返回所有省,也可以通过省名指定单个省或多个省。
- `get_cn_city`:读取市界。默认返回所有市。
  - 通过市名指定单个市或多个市
  - 通过省名指定单个省或多个省包含的所有市
- `get_cn_district`:读取县界。默认返回所有县。
  - 通过县名指定单个县或多个县
  - 通过市名指定单个市或多个市包含的所有县
  - 通过省名指定单个省或多个省包含的所有县

```python
import frykit.shp as fshp

国界 = fshp.get_cn_border()
九段线 = fshp.get_cn_line()

所有省 = fshp.get_cn_province()
安徽省 = fshp.get_cn_province('安徽省')
安徽省, 江苏省 = fshp.get_cn_province(['安徽省', '江苏省'])

所有市 = fshp.get_cn_city()
合肥市 = fshp.get_cn_city('合肥市')
合肥市, 六安市 = fshp.get_cn_city(['合肥市', '六安市'])

安徽省的所有市 = fshp.get_cn_city(province='安徽省')
安徽省和江苏省的所有市 = fshp.get_cn_city(province=['安徽省', '江苏省'])

所有区县 = fshp.get_cn_district()
蜀山区 = fshp.get_cn_district('蜀山区')
蜀山区, 包河区 = fshp.get_cn_district(['蜀山区', '包河区'])

合肥市的所有区县 = fshp.get_cn_district(city='合肥市')
安徽省的所有区县 = fshp.get_cn_district(province='安徽省')
```

除了用字符串名称,也可以用行政区划代码(adcode)查询:

```python
北京市 = fshp.get_cn_province(110000)
京津冀 = fshp.get_cn_province([110000, 120000, 130000])
```

### 切换数据源

> 0.7 版本新增

内置两套中国行政区划数据:高德地图行政区划 API 和天地图公开的可视化数据。区别是:

- 高德数据更精细;天地图数据更精简,画图更快。
- 市级和县级区划有差异,例如天地图有台湾的区县。
- 高德数据存在飞地,例如内蒙古境内有黑龙江的飞地加格达奇区([issue#5](https://github.com/ZhaJiMan/frykit/issues/5))。
- 高德数据的直辖市在市级的名称从 XX 市变为 XX 城区,adcode 也不同。

推荐使用天地图数据,因为画图更快、没有飞地问题,且官网带一个审图号。但因为旧版本只有高德数据,所以为了兼容性而默认使用高德数据。切换数据源的方法有:

```python
# 通过函数参数指定
amap_cities = fshp.get_cn_city(data_source='amap')
tianditu_cities = fshp.get_cn_city(data_source='tianditu')

# 在脚本开头设置全局数据源
import frykit
frykit.config.data_source = 'tianditu'

# 用上下文管理器临时设置数据源
with frykit.config.context(data_source='amap'):
    amap_cities = fshp.get_cn_city()
with frykit.config.context(data_source='tianditu'):
    tianditu_cities = fshp.get_cn_city()
```

> 0.7.5 版本开始用 `frykit.config` 进行配置,之前用 `frykit.option`

具体数据说明见 [frykit_data](https://github.com/ZhaJiMan/frykit_data) 仓库。

### 绘制中国行政区划

- `add_cn_border`:绘制国界
- `add_cn_line`:绘制九段线
- `add_cn_province`:绘制省界
- `add_cn_city`:绘制市界
- `add_cn_district`:绘制县界

另外还提供标注名字的函数:

- `label_cn_province`:标注省名
- `label_cn_city`:标注市名
- `label_cn_district`:标注县名

同样可以用 `data_source` 参数切换数据源。

画出所有省份,同时用颜色区分京津冀地区:

```python
import matplotlib.pyplot as plt
import frykit.plot as fplt

plt.figure(figsize=(8, 8))
ax = plt.axes(projection=fplt.PLATE_CARREE)
fplt.add_cn_province(ax)
fplt.add_cn_province(ax, ['北京市', '天津市', '河北省'], fc='dodgerblue')
fplt.add_cn_line(ax)
fplt.label_cn_province(ax)

plt.show()
```

其中 `fplt.PLATE_CARREE` 只是一个 `ccrs.PlateCarree()` 实例,这样就无需在脚本开头导入 `cartopy.crs`。

![add_cn_province](image/add_cn_province.png)

画出北京所有区:

```python
import matplotlib.pyplot as plt
import frykit.plot as fplt

ax = plt.axes(projection=fplt.PLATE_CARREE)
fplt.add_cn_district(ax, province='北京市', fc=plt.cm.Pastel1.colors)
fplt.label_cn_district(ax, province='北京市')

plt.show()
```

![beijing](image/beijing.png)

### 绘制世界底图

```python
# 画所有国家
fplt.add_countries(ax)

# 画海陆
fplt.add_land(ax, fc='floralwhite')
fplt.add_ocean(ax, fc='dodgerblue')
```

外国和海陆数据并不精细,仅供试用。

### 绘制任意多边形

`add_cn_border` 函数相当于

```python
fplt.add_geometries(ax, fshp.get_cn_border())
```

底层的 `add_geometries` 函数类似 Cartopy 的 `GeoAxes.add_geometries`,可以绘制 Shapely 的 `LineString` 和 `Polygon` 对象。区别是能用于普通的 `Axes`,并且对投影速度和填色有优化。

画一个半径为 10 的圆:

```python
import shapely

circle = shapely.Point(0, 0).buffer(10)
fplt.add_geometries(ax, circle)
```

画自己的 shapefile:

```python
from cartopy.io.shapereader import Reader

reader = Reader('2023年_CTAmap_1.12版/2023年县级/2023年县级.shp')
geometries = list(reader.geometries())
reader.close()

fplt.add_geometries(ax, geometries, fc='none', ec='k', lw=0.25)
```

画自己的 GeoJSON:

```python
import json
import shapely.geometry as sgeom

with open('天地图_行政区划可视化/中国_省.geojson') as f:
    geojson_dict = json.load(f)

features = geojson_dict['features']
geometries = sgeom.shape(feature['geometry']) for feature in features
fplt.add_geometries(ax, geometries, fc='none', ec='k', lw=0.25)
```

通过 `array`、 `cmap` 和 `norm` 参数还能实现类似分省填色的效果(详见 [fill.py](example/fill.py))。

`add_geometries` 默认直接用 pyproj 做地图投影变换,速度更快但也更容易出现错误的效果。可以指定参数 `fast_transform=False`,切换成更正确但速度更慢的模式;或者改用 `GeoAxes.add_geometries`。例如:

```python
# 通过函数参数指定
fplt.add_geometries(ax, geometries, fast_transform=False)
fplt.add_cn_province(ax, fast_transform=False)

# 在脚本开头设置全局
frykit.config.fast_transform = False
```

### 裁剪 Artist

这里 Artist 泛指 Matplotlib 里 `contourf`、 `pcolormesh`、 `imshow`、 `quiver`、 `scatter` 等方法返回的对象。

- `clip_by_cn_border`:用国界裁剪。
- `clip_by_cn_province`:用省界裁剪。
- `clip_by_cn_city`:用市界裁剪。
- `clip_by_cn_district`:用县界裁剪。
- `clip_by_polygon`:用任意多边形裁剪。

用国界裁剪 `contourf` 的例子:

```python
import matplotlib.pyplot as plt
import frykit.plot as fplt

ax = plt.axes(projection=fplt.PLATE_CARREE)
fplt.add_cn_province(ax)
fplt.add_cn_line(ax)

data = fplt.load_test_data()
cf = ax.contourf(
    data.lon,
    data.lat,
    data.t2m,
    levels=20,
    cmap='rainbow',
    transform=fplt.PLATE_CARREE,
)
fplt.clip_by_cn_border(cf)

plt.show()
```

![clip_by_cn_border](image/clip_by_cn_border.png)

多省裁剪直接传入列表即可:

```python
fplt.clip_by_cn_province(artist, ['北京市', '天津市', '河北省'])
```

更复杂的裁剪需要手动处理多边形:

```python
北京市 = fshp.get_cn_province('北京市')
保定市 = fshp.get_cn_city('保定市')
张家口市 = fshp.get_cn_city('张家口市')
polygon = shapely.union_all([北京市, 保定市, 张家口市])
fplt.clip_by_polygon(artist, polygon)
```

### 制作掩膜

裁剪是在画图阶段从视觉效果上屏蔽多边形外的数据,而掩膜则是在数据处理阶段对多边形外的数据进行处理,例如设为缺测。

```python
border = fshp.get_cn_border()
mask = fshp.polygon_mask(border, lon2d, lat2d)
data[~mask] = np.nan
ax.contourf(lon2d, lat2d, data)
```

如果数据坐标能用二维直线网格描述,那么还提供更优化的 `polygon_mask2` 函数:

```python
mask = fshp.polygon_mask2(border, lon1d, lat1d)
data[~mask] = np.nan
```

### 设置地图范围和刻度

`GeoAxes` 设置地图范围和刻度需要以下步骤:

```python
import numpy as np
import cartopy.crs as ccrs
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter

crs = ccrs.PlateCarree()
ax.set_extent((70, 140, 0, 60), crs=crs)
ax.set_xticks(np.arange(70, 141, 10), crs=crs)
ax.set_yticks(np.arange(0, 61, 10), crs=crs)
ax.xaxis.set_major_formatter(LongitudeFormatter())
ax.yaxis.set_major_formatter(LatitudeFormatter())
```

`set_map_ticks` 函数可以将这段简化成一行:

```python
fplt.set_map_ticks(ax, (70, 140, 0, 60), dx=10, dy=10)
```

会自动根据经度间隔和纬度间隔生成刻度,并加上度数和东南西北的符号。另外还可以:

* 用 `xticks` 和 `yticks` 显式指定刻度。
* 用 `mx` 和 `my` 参数指定次刻度的数量。
* 适用于非等经纬度投影。

> 对于非等经纬度投影的 `GeoAxes`,如果显示范围不是矩形,或者范围跨越了 180 度经线,该函数可能产生错误的效果。

### 添加风矢量图例

在右下角添加一个白色矩形背景的风矢量图例:

```python
Q = ax.quiver(x, y, u, v, transform=fplt.PLATE_CARREE)
fplt.add_quiver_legend(Q, U=10, width=0.15, height=0.12)
```

### 添加指北针

```python
fplt.add_compass(ax, 0.95, 0.8, size=15)
```

指北针的位置基于 `Axes` 坐标系。 `ax` 是 `GeoAxes` 时指北针会自动指向所在位置处的北向,也可以通过 `angle` 参数手动指定角度。

### 添加比例尺

```python
scale_bar = fplt.add_scale_bar(ax, 0.36, 0.8, length=1000)
scale_bar.set_xticks([0, 500, 1000])
```

比例尺的长度通过采样 `GeoAxes` 中心处单位长度对应的地理距离得出。比例尺对象类似 `Axes`,可以用 `set_xticks` 等方法进一步修改样式。

### 添加小地图

```python
mini_ax = fplt.add_mini_axes(ax)
mini_ax.set_extent((105, 120, 2, 25), crs=fplt.PLATE_CARREE)
fplt.add_cn_province(mini_ax)
fplt.add_cn_line(mini_ax)
```

小地图默认使用大地图的投影,会自动定位到大地图的角落,无需像 `add_axes` 那样需要反复调整位置。

### GMT 风格边框

```python
fplt.add_frame(ax)
```

添加类似 [GMT](https://www.generic-mapping-tools.org/) 风格的黑白相间格子的边框。目前仅支持等经纬度或墨卡托投影的 `GeoAxes`。

也可以用来制作 GMT 风格的比例尺:

```python
fplt.add_frame(scale_bar)
```

### 特殊 colorbar

构造一个颜色对应一个刻度的 colorbar:

```python
colors = [
    'orangered',
    'orange',
    'yellow',
    'limegreen',
    'royalblue',
    'darkviolet'
]
cmap, norm, ticks = fplt.make_qualitative_palette(colors)
cbar = fplt.plot_colormap(cmap, norm)
cbar.set_ticks(ticks)
cbar.set_ticklabels(colors)
```

构造零值所在区间对应白色的 colorbar:

```python
import cmaps

boundaries = [-10, -5, -2, -1, 1, 2, 5, 10, 20, 50, 100]
norm = fplt.CenteredBoundaryNorm(boundaries)
cbar = fplt.plot_colormap(cmaps.BlueWhiteOrangeRed, norm)
cbar.set_ticks(boundaries)
```

![colorbar](image/colorbar.png)

## 性能测试

Cartopy 和 frykit 绘制行政区划的耗时如下表所示:

<table><thead><tr><th rowspan="2">范围</th><th rowspan="2">区划</th><th colspan="3">cartopy</th><th colspan="3">frykit</th></tr><tr><th>1</th><th>2</th><th>3</th><th>1</th><th>2</th><th>3</th></tr></thead><tbody><tr><td rowspan="4">全国</td><td>国</td><td>3.48</td><td>0.16</td><td>0.18</td><td>0.4</td><td>0.2</td><td>0.16</td></tr><tr><td>省</td><td>12.43</td><td>0.32</td><td>0.28</td><td>0.75</td><td>0.26</td><td>0.25</td></tr><tr><td>市</td><td>31.54</td><td>0.48</td><td>0.48</td><td>1.61</td><td>0.5</td><td>0.48</td></tr><tr><td>县</td><td>73.05</td><td>1.0</td><td>0.93</td><td>3.71</td><td>1.03</td><td>0.95</td></tr><tr><td rowspan="4">南方</td><td>国</td><td>3.16</td><td>0.14</td><td>0.15</td><td>0.33</td><td>0.14</td><td>0.2</td></tr><tr><td>省</td><td>11.85</td><td>0.17</td><td>0.16</td><td>0.29</td><td>0.14</td><td>0.15</td></tr><tr><td>市</td><td>31.35</td><td>0.2</td><td>0.22</td><td>0.46</td><td>0.16</td><td>0.16</td></tr><tr><td>县</td><td>76.59</td><td>0.34</td><td>0.33</td><td>0.77</td><td>0.24</td><td>0.24</td></tr></tbody></table>

```
# 环境版本
python==3.13.5
cartopy==0.25.0
frykit==0.7.6
```

- 使用高德数据
- 使用等距方位投影,以体现直接用 pyproj 和用 Cartopoy 做投影的时间差异。
- `cartopy>=0.23` 时即便 `GeoAxes` 的范围很小,仍需要对范围外的所有多边形做投影,导致速度很慢。而 frykit 对此有优化。因此这里设置两种地图范围:全国 `(70, 140, 0, 60)` 和南方 `(115, 120, 24, 28)`。
- 比较连续画三张图的结果,缓存机制会使第一次耗时最长,后续耗时大幅缩短。

测试脚本见 [script/measure_time.py](script/measure_time.py)

## 详细介绍

工具箱的原理和使用场景可见下面几篇博文:

- [Cartopy 系列:画中国地图的工具箱 frykit](https://zhajiman.github.io/post/frykit/)
- [Cartopy 系列:探索 shapefile](https://zhajiman.github.io/post/cartopy_shapefile/)
- [Cartopy 系列:裁剪填色图出界问题](https://zhajiman.github.io/post/cartopy_clip_outside/)
- [Cartopy 添加南海小地图的三种方法](https://mp.weixin.qq.com/s/-QMVN6MS-UuQ9lQjz9vqBQ)
- [Matplotlib 系列:colormap 的设置](https://zhajiman.github.io/post/matplotlib_colormap/)
- [天地图“带审图号”的行政区划数据](http://bbs.06climate.com/forum.php?mod=viewthread&tid=109893)

## 示例效果

仓库的 `example` 目录里有更复杂的示例脚本:

- [在普通 `Axes` 上画地图](example/axes.py)

![axes](image/axes.png)

- [分省填色](example/fill.py)

![fill](image/fill.png)

- [裁剪 `contourf` 和 `quiver`](example/quiver.py)

![quiver](image/quiver.png)

- [裁剪出界处理](example/strict_clip.py)

![strict_clip](image/strict_clip.png)

- [裁剪主图和南海小图的 `contourf`](example/contourf.py)

![contourf](image/contourf.png)

- [模仿 NERV 风格的地图](example/nerv_style.py)

![nerv_style](image/nerv_style.png)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "frykit",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "ZhaJiMan <915023793@qq.com>",
    "download_url": "https://files.pythonhosted.org/packages/ef/42/794d0ae6093b2c1b832cce8f7538366aff7dc4eea3fb0a1e336af4da5967/frykit-0.7.6.tar.gz",
    "platform": null,
    "description": "# frykit\r\n\r\n\u4e00\u4e2a\u914d\u5408 Matplotlib \u548c Cartopy \u4f7f\u7528\u7684\u5730\u56fe\u5de5\u5177\u7bb1\uff0c\u4e3b\u8981\u7531 `shp` \u548c `plot` \u6a21\u5757\u7ec4\u6210\u3002\r\n\r\n`shp` \u6a21\u5757\u7684\u529f\u80fd\u662f\uff1a\r\n\r\n- \u8bfb\u53d6\u4e2d\u56fd\u884c\u653f\u533a\u5212\u6570\u636e\r\n- \u521b\u5efa\u591a\u8fb9\u5f62\u63a9\u819c\uff08mask\uff09\r\n\r\n`plot` \u6a21\u5757\u7684\u529f\u80fd\u5305\u62ec\uff1a\r\n\r\n- \u7ed8\u5236\u4e2d\u56fd\u884c\u653f\u533a\u5212\u6570\u636e\u3002\r\n- \u5229\u7528\u884c\u653f\u533a\u5212\u505a\u88c1\u526a\uff08clip\uff09\r\n- \u5feb\u901f\u8bbe\u7f6e\u5730\u56fe\u8303\u56f4\u548c\u523b\u5ea6\r\n- \u6dfb\u52a0\u5357\u6d77\u5c0f\u56fe\r\n- \u6dfb\u52a0\u98ce\u77e2\u91cf\u56fe\u7684\u56fe\u4f8b\r\n- \u6dfb\u52a0\u6307\u5317\u9488\r\n- \u6dfb\u52a0\u6bd4\u4f8b\u5c3a\r\n\r\n\u7279\u8272\u662f\uff1a\r\n\r\n- \u81ea\u5e26\u9ad8\u5fb7\u5730\u56fe\u548c\u5929\u5730\u56fe\u7684\u884c\u653f\u533a\u5212\u6570\u636e\r\n- \u53ef\u540c\u65f6\u7528\u4e8e `Axes` \u548c `GeoAxes`\r\n- \u5bf9\u753b\u56fe\u901f\u5ea6\u6709\u4f18\u5316\r\n- \u5bf9\u88c1\u526a\u51fa\u754c\u95ee\u9898\u6709\u4f18\u5316\r\n\r\n\u6682\u65e0\u6587\u6863\uff0c\u4f46\u662f\u6bcf\u4e2a\u51fd\u6570\u90fd\u6709\u8be6\u7ec6\u7684 docstring\uff0c\u53ef\u4ee5\u5728 Python \u547d\u4ee4\u884c\u4e2d\u901a\u8fc7 `help` \u51fd\u6570\u67e5\u770b\uff0c\u6216\u8005\u5728 IDE \u4e2d\u67e5\u770b\u3002\r\n\r\n\u8fd9\u4e2a\u5305\u53ea\u662f\u4f5c\u8005\u81ea\u7528\u7684\u5c0f\u5de5\u5177\u96c6\uff0c\u51fd\u6570\u7f16\u5199\u7c97\u7cd9\uff0c\u53ef\u80fd\u5b58\u5728\u4e0d\u5c11 bug\uff0c\u8fd8\u8bf7\u591a\u591a\u4ea4\u6d41\u6307\u6b63\u3002\u7c7b\u4f3c\u7684\u66f4\u5b8c\u5907\u7684\u5305\u8fd8\u8bf7\u79fb\u6b65 [cnmaps](https://github.com/cnmetlab/cnmaps)\u3001[gma](https://gma.luosgeo.com/) \u6216 [EOmaps](https://github.com/raphaelquast/EOmaps)\u3002\r\n\r\n> \u6709\u95ee\u9898\u76f4\u63a5\u63d0 issue\uff0c\u4e5f\u53ef\u4ee5\u52a0\u4ea4\u6d41 QQ \u7fa4\uff1a1017694471\r\n\r\n## \u5b89\u88c5\r\n\r\n```\r\n# \u53ea\u9700\u8981 frykit \u7684\u5de5\u5177\u51fd\u6570\r\npip install frykit\r\n\r\n# \u9700\u8981\u5730\u56fe\u6570\u636e\r\npip install frykit[data]\r\n\r\n# \u66f4\u65b0\r\npip install -U frykit\r\n```\r\n\r\n0.7.0 \u5f00\u59cb\u5730\u56fe\u6570\u636e\u548c frykit \u672c\u4f53\u5206\u79bb\uff0c\u5b8c\u6574\u5b89\u88c5\u548c\u66f4\u65b0\u9700\u8981\u6307\u5b9a `frykit[data]`\uff0c\u800c\u4e4b\u524d\u7684\u7248\u672c\u7b80\u5355 pip \u5b89\u88c5\u5373\u53ef\u3002\r\n\r\n\u5177\u4f53\u4f9d\u8d56\u4e3a\uff1a\r\n\r\n```\r\npython>=3.10.0\r\npandas>=2.0.0\r\nshapely>=2.0.0\r\ncartopy>=0.22.0\r\n```\r\n\r\nPython \u7248\u672c\u4e0d\u6ee1\u8db3\u8981\u6c42\u65f6\u53ef\u80fd\u88c5\u4e0a\u8001\u7248\u672c\u7684 frykit\uff0c\u7f3a\u5c11\u65b0\u51fd\u6570\u6216\u8005\u5728\u8fd0\u884c\u65f6\u62a5\u9519\u3002\r\n\r\n## \u66f4\u65b0\u8bb0\u5f55\r\n\r\n[CHANGELOG.md](CHANGELOG.md)\r\n\r\n## \u4f7f\u7528\u6307\u5357\r\n\r\n### \u8bfb\u53d6\u4e2d\u56fd\u884c\u653f\u533a\u5212\r\n\r\n`get_cn_xxx` \u7cfb\u5217\u51fd\u6570\u80fd\u8bfb\u53d6\u4e2d\u56fd\u884c\u653f\u533a\u5212\uff0c\u8fd4\u56de [Shapely](https://shapely.readthedocs.io/en/stable/manual.html) \u591a\u8fb9\u5f62\u5bf9\u8c61\u3002\u5177\u4f53\u6765\u8bf4\uff1a\r\n\r\n- `get_cn_border`\uff1a\u8bfb\u53d6\u56fd\u754c\r\n- `get_cn_line`\uff1a\u8bfb\u53d6\u4e5d\u6bb5\u7ebf\r\n- `get_cn_province`\uff1a\u8bfb\u53d6\u7701\u754c\u3002\u9ed8\u8ba4\u8fd4\u56de\u6240\u6709\u7701\uff0c\u4e5f\u53ef\u4ee5\u901a\u8fc7\u7701\u540d\u6307\u5b9a\u5355\u4e2a\u7701\u6216\u591a\u4e2a\u7701\u3002\r\n- `get_cn_city`\uff1a\u8bfb\u53d6\u5e02\u754c\u3002\u9ed8\u8ba4\u8fd4\u56de\u6240\u6709\u5e02\u3002\r\n  - \u901a\u8fc7\u5e02\u540d\u6307\u5b9a\u5355\u4e2a\u5e02\u6216\u591a\u4e2a\u5e02\r\n  - \u901a\u8fc7\u7701\u540d\u6307\u5b9a\u5355\u4e2a\u7701\u6216\u591a\u4e2a\u7701\u5305\u542b\u7684\u6240\u6709\u5e02\r\n- `get_cn_district`\uff1a\u8bfb\u53d6\u53bf\u754c\u3002\u9ed8\u8ba4\u8fd4\u56de\u6240\u6709\u53bf\u3002\r\n  - \u901a\u8fc7\u53bf\u540d\u6307\u5b9a\u5355\u4e2a\u53bf\u6216\u591a\u4e2a\u53bf\r\n  - \u901a\u8fc7\u5e02\u540d\u6307\u5b9a\u5355\u4e2a\u5e02\u6216\u591a\u4e2a\u5e02\u5305\u542b\u7684\u6240\u6709\u53bf\r\n  - \u901a\u8fc7\u7701\u540d\u6307\u5b9a\u5355\u4e2a\u7701\u6216\u591a\u4e2a\u7701\u5305\u542b\u7684\u6240\u6709\u53bf\r\n\r\n```python\r\nimport frykit.shp as fshp\r\n\r\n\u56fd\u754c = fshp.get_cn_border()\r\n\u4e5d\u6bb5\u7ebf = fshp.get_cn_line()\r\n\r\n\u6240\u6709\u7701 = fshp.get_cn_province()\r\n\u5b89\u5fbd\u7701 = fshp.get_cn_province('\u5b89\u5fbd\u7701')\r\n\u5b89\u5fbd\u7701, \u6c5f\u82cf\u7701 = fshp.get_cn_province(['\u5b89\u5fbd\u7701', '\u6c5f\u82cf\u7701'])\r\n\r\n\u6240\u6709\u5e02 = fshp.get_cn_city()\r\n\u5408\u80a5\u5e02 = fshp.get_cn_city('\u5408\u80a5\u5e02')\r\n\u5408\u80a5\u5e02, \u516d\u5b89\u5e02 = fshp.get_cn_city(['\u5408\u80a5\u5e02', '\u516d\u5b89\u5e02'])\r\n\r\n\u5b89\u5fbd\u7701\u7684\u6240\u6709\u5e02 = fshp.get_cn_city(province='\u5b89\u5fbd\u7701')\r\n\u5b89\u5fbd\u7701\u548c\u6c5f\u82cf\u7701\u7684\u6240\u6709\u5e02 = fshp.get_cn_city(province=['\u5b89\u5fbd\u7701', '\u6c5f\u82cf\u7701'])\r\n\r\n\u6240\u6709\u533a\u53bf = fshp.get_cn_district()\r\n\u8700\u5c71\u533a = fshp.get_cn_district('\u8700\u5c71\u533a')\r\n\u8700\u5c71\u533a, \u5305\u6cb3\u533a = fshp.get_cn_district(['\u8700\u5c71\u533a', '\u5305\u6cb3\u533a'])\r\n\r\n\u5408\u80a5\u5e02\u7684\u6240\u6709\u533a\u53bf = fshp.get_cn_district(city='\u5408\u80a5\u5e02')\r\n\u5b89\u5fbd\u7701\u7684\u6240\u6709\u533a\u53bf = fshp.get_cn_district(province='\u5b89\u5fbd\u7701')\r\n```\r\n\r\n\u9664\u4e86\u7528\u5b57\u7b26\u4e32\u540d\u79f0\uff0c\u4e5f\u53ef\u4ee5\u7528\u884c\u653f\u533a\u5212\u4ee3\u7801\uff08adcode\uff09\u67e5\u8be2\uff1a\r\n\r\n```python\r\n\u5317\u4eac\u5e02 = fshp.get_cn_province(110000)\r\n\u4eac\u6d25\u5180 = fshp.get_cn_province([110000, 120000, 130000])\r\n```\r\n\r\n### \u5207\u6362\u6570\u636e\u6e90\r\n\r\n> 0.7 \u7248\u672c\u65b0\u589e\r\n\r\n\u5185\u7f6e\u4e24\u5957\u4e2d\u56fd\u884c\u653f\u533a\u5212\u6570\u636e\uff1a\u9ad8\u5fb7\u5730\u56fe\u884c\u653f\u533a\u5212 API \u548c\u5929\u5730\u56fe\u516c\u5f00\u7684\u53ef\u89c6\u5316\u6570\u636e\u3002\u533a\u522b\u662f\uff1a\r\n\r\n- \u9ad8\u5fb7\u6570\u636e\u66f4\u7cbe\u7ec6\uff1b\u5929\u5730\u56fe\u6570\u636e\u66f4\u7cbe\u7b80\uff0c\u753b\u56fe\u66f4\u5feb\u3002\r\n- \u5e02\u7ea7\u548c\u53bf\u7ea7\u533a\u5212\u6709\u5dee\u5f02\uff0c\u4f8b\u5982\u5929\u5730\u56fe\u6709\u53f0\u6e7e\u7684\u533a\u53bf\u3002\r\n- \u9ad8\u5fb7\u6570\u636e\u5b58\u5728\u98de\u5730\uff0c\u4f8b\u5982\u5185\u8499\u53e4\u5883\u5185\u6709\u9ed1\u9f99\u6c5f\u7684\u98de\u5730\u52a0\u683c\u8fbe\u5947\u533a\uff08[issue#5](https://github.com/ZhaJiMan/frykit/issues/5)\uff09\u3002\r\n- \u9ad8\u5fb7\u6570\u636e\u7684\u76f4\u8f96\u5e02\u5728\u5e02\u7ea7\u7684\u540d\u79f0\u4ece XX \u5e02\u53d8\u4e3a XX \u57ce\u533a\uff0cadcode \u4e5f\u4e0d\u540c\u3002\r\n\r\n\u63a8\u8350\u4f7f\u7528\u5929\u5730\u56fe\u6570\u636e\uff0c\u56e0\u4e3a\u753b\u56fe\u66f4\u5feb\u3001\u6ca1\u6709\u98de\u5730\u95ee\u9898\uff0c\u4e14\u5b98\u7f51\u5e26\u4e00\u4e2a\u5ba1\u56fe\u53f7\u3002\u4f46\u56e0\u4e3a\u65e7\u7248\u672c\u53ea\u6709\u9ad8\u5fb7\u6570\u636e\uff0c\u6240\u4ee5\u4e3a\u4e86\u517c\u5bb9\u6027\u800c\u9ed8\u8ba4\u4f7f\u7528\u9ad8\u5fb7\u6570\u636e\u3002\u5207\u6362\u6570\u636e\u6e90\u7684\u65b9\u6cd5\u6709\uff1a\r\n\r\n```python\r\n# \u901a\u8fc7\u51fd\u6570\u53c2\u6570\u6307\u5b9a\r\namap_cities = fshp.get_cn_city(data_source='amap')\r\ntianditu_cities = fshp.get_cn_city(data_source='tianditu')\r\n\r\n# \u5728\u811a\u672c\u5f00\u5934\u8bbe\u7f6e\u5168\u5c40\u6570\u636e\u6e90\r\nimport frykit\r\nfrykit.config.data_source = 'tianditu'\r\n\r\n# \u7528\u4e0a\u4e0b\u6587\u7ba1\u7406\u5668\u4e34\u65f6\u8bbe\u7f6e\u6570\u636e\u6e90\r\nwith frykit.config.context(data_source='amap'):\r\n    amap_cities = fshp.get_cn_city()\r\nwith frykit.config.context(data_source='tianditu'):\r\n    tianditu_cities = fshp.get_cn_city()\r\n```\r\n\r\n> 0.7.5 \u7248\u672c\u5f00\u59cb\u7528 `frykit.config` \u8fdb\u884c\u914d\u7f6e\uff0c\u4e4b\u524d\u7528 `frykit.option`\r\n\r\n\u5177\u4f53\u6570\u636e\u8bf4\u660e\u89c1 [frykit_data](https://github.com/ZhaJiMan/frykit_data) \u4ed3\u5e93\u3002\r\n\r\n### \u7ed8\u5236\u4e2d\u56fd\u884c\u653f\u533a\u5212\r\n\r\n- `add_cn_border`\uff1a\u7ed8\u5236\u56fd\u754c\r\n- `add_cn_line`\uff1a\u7ed8\u5236\u4e5d\u6bb5\u7ebf\r\n- `add_cn_province`\uff1a\u7ed8\u5236\u7701\u754c\r\n- `add_cn_city`\uff1a\u7ed8\u5236\u5e02\u754c\r\n- `add_cn_district`\uff1a\u7ed8\u5236\u53bf\u754c\r\n\r\n\u53e6\u5916\u8fd8\u63d0\u4f9b\u6807\u6ce8\u540d\u5b57\u7684\u51fd\u6570\uff1a\r\n\r\n- `label_cn_province`\uff1a\u6807\u6ce8\u7701\u540d\r\n- `label_cn_city`\uff1a\u6807\u6ce8\u5e02\u540d\r\n- `label_cn_district`\uff1a\u6807\u6ce8\u53bf\u540d\r\n\r\n\u540c\u6837\u53ef\u4ee5\u7528 `data_source` \u53c2\u6570\u5207\u6362\u6570\u636e\u6e90\u3002\r\n\r\n\u753b\u51fa\u6240\u6709\u7701\u4efd\uff0c\u540c\u65f6\u7528\u989c\u8272\u533a\u5206\u4eac\u6d25\u5180\u5730\u533a\uff1a\r\n\r\n```python\r\nimport matplotlib.pyplot as plt\r\nimport frykit.plot as fplt\r\n\r\nplt.figure(figsize=(8, 8))\r\nax = plt.axes(projection=fplt.PLATE_CARREE)\r\nfplt.add_cn_province(ax)\r\nfplt.add_cn_province(ax, ['\u5317\u4eac\u5e02', '\u5929\u6d25\u5e02', '\u6cb3\u5317\u7701'], fc='dodgerblue')\r\nfplt.add_cn_line(ax)\r\nfplt.label_cn_province(ax)\r\n\r\nplt.show()\r\n```\r\n\r\n\u5176\u4e2d `fplt.PLATE_CARREE` \u53ea\u662f\u4e00\u4e2a `ccrs.PlateCarree()` \u5b9e\u4f8b\uff0c\u8fd9\u6837\u5c31\u65e0\u9700\u5728\u811a\u672c\u5f00\u5934\u5bfc\u5165 `cartopy.crs`\u3002\r\n\r\n![add_cn_province](image/add_cn_province.png)\r\n\r\n\u753b\u51fa\u5317\u4eac\u6240\u6709\u533a\uff1a\r\n\r\n```python\r\nimport matplotlib.pyplot as plt\r\nimport frykit.plot as fplt\r\n\r\nax = plt.axes(projection=fplt.PLATE_CARREE)\r\nfplt.add_cn_district(ax, province='\u5317\u4eac\u5e02', fc=plt.cm.Pastel1.colors)\r\nfplt.label_cn_district(ax, province='\u5317\u4eac\u5e02')\r\n\r\nplt.show()\r\n```\r\n\r\n![beijing](image/beijing.png)\r\n\r\n### \u7ed8\u5236\u4e16\u754c\u5e95\u56fe\r\n\r\n```python\r\n# \u753b\u6240\u6709\u56fd\u5bb6\r\nfplt.add_countries(ax)\r\n\r\n# \u753b\u6d77\u9646\r\nfplt.add_land(ax, fc='floralwhite')\r\nfplt.add_ocean(ax, fc='dodgerblue')\r\n```\r\n\r\n\u5916\u56fd\u548c\u6d77\u9646\u6570\u636e\u5e76\u4e0d\u7cbe\u7ec6\uff0c\u4ec5\u4f9b\u8bd5\u7528\u3002\r\n\r\n### \u7ed8\u5236\u4efb\u610f\u591a\u8fb9\u5f62\r\n\r\n`add_cn_border` \u51fd\u6570\u76f8\u5f53\u4e8e\r\n\r\n```python\r\nfplt.add_geometries(ax, fshp.get_cn_border())\r\n```\r\n\r\n\u5e95\u5c42\u7684 `add_geometries` \u51fd\u6570\u7c7b\u4f3c Cartopy \u7684 `GeoAxes.add_geometries`\uff0c\u53ef\u4ee5\u7ed8\u5236 Shapely \u7684 `LineString` \u548c `Polygon` \u5bf9\u8c61\u3002\u533a\u522b\u662f\u80fd\u7528\u4e8e\u666e\u901a\u7684 `Axes`\uff0c\u5e76\u4e14\u5bf9\u6295\u5f71\u901f\u5ea6\u548c\u586b\u8272\u6709\u4f18\u5316\u3002\r\n\r\n\u753b\u4e00\u4e2a\u534a\u5f84\u4e3a 10 \u7684\u5706\uff1a\r\n\r\n```python\r\nimport shapely\r\n\r\ncircle = shapely.Point(0, 0).buffer(10)\r\nfplt.add_geometries(ax, circle)\r\n```\r\n\r\n\u753b\u81ea\u5df1\u7684 shapefile\uff1a\r\n\r\n```python\r\nfrom cartopy.io.shapereader import Reader\r\n\r\nreader = Reader('2023\u5e74_CTAmap_1.12\u7248/2023\u5e74\u53bf\u7ea7/2023\u5e74\u53bf\u7ea7.shp')\r\ngeometries = list(reader.geometries())\r\nreader.close()\r\n\r\nfplt.add_geometries(ax, geometries, fc='none', ec='k', lw=0.25)\r\n```\r\n\r\n\u753b\u81ea\u5df1\u7684 GeoJSON\uff1a\r\n\r\n```python\r\nimport json\r\nimport shapely.geometry as sgeom\r\n\r\nwith open('\u5929\u5730\u56fe_\u884c\u653f\u533a\u5212\u53ef\u89c6\u5316/\u4e2d\u56fd_\u7701.geojson') as f:\r\n    geojson_dict = json.load(f)\r\n\r\nfeatures = geojson_dict['features']\r\ngeometries = sgeom.shape(feature['geometry']) for feature in features\r\nfplt.add_geometries(ax, geometries, fc='none', ec='k', lw=0.25)\r\n```\r\n\r\n\u901a\u8fc7 `array`\u3001 `cmap` \u548c `norm` \u53c2\u6570\u8fd8\u80fd\u5b9e\u73b0\u7c7b\u4f3c\u5206\u7701\u586b\u8272\u7684\u6548\u679c\uff08\u8be6\u89c1 [fill.py](example/fill.py)\uff09\u3002\r\n\r\n`add_geometries` \u9ed8\u8ba4\u76f4\u63a5\u7528 pyproj \u505a\u5730\u56fe\u6295\u5f71\u53d8\u6362\uff0c\u901f\u5ea6\u66f4\u5feb\u4f46\u4e5f\u66f4\u5bb9\u6613\u51fa\u73b0\u9519\u8bef\u7684\u6548\u679c\u3002\u53ef\u4ee5\u6307\u5b9a\u53c2\u6570 `fast_transform=False`\uff0c\u5207\u6362\u6210\u66f4\u6b63\u786e\u4f46\u901f\u5ea6\u66f4\u6162\u7684\u6a21\u5f0f\uff1b\u6216\u8005\u6539\u7528 `GeoAxes.add_geometries`\u3002\u4f8b\u5982\uff1a\r\n\r\n```python\r\n# \u901a\u8fc7\u51fd\u6570\u53c2\u6570\u6307\u5b9a\r\nfplt.add_geometries(ax, geometries, fast_transform=False)\r\nfplt.add_cn_province(ax, fast_transform=False)\r\n\r\n# \u5728\u811a\u672c\u5f00\u5934\u8bbe\u7f6e\u5168\u5c40\r\nfrykit.config.fast_transform = False\r\n```\r\n\r\n### \u88c1\u526a Artist\r\n\r\n\u8fd9\u91cc Artist \u6cdb\u6307 Matplotlib \u91cc `contourf`\u3001 `pcolormesh`\u3001 `imshow`\u3001 `quiver`\u3001 `scatter` \u7b49\u65b9\u6cd5\u8fd4\u56de\u7684\u5bf9\u8c61\u3002\r\n\r\n- `clip_by_cn_border`\uff1a\u7528\u56fd\u754c\u88c1\u526a\u3002\r\n- `clip_by_cn_province`\uff1a\u7528\u7701\u754c\u88c1\u526a\u3002\r\n- `clip_by_cn_city`\uff1a\u7528\u5e02\u754c\u88c1\u526a\u3002\r\n- `clip_by_cn_district`\uff1a\u7528\u53bf\u754c\u88c1\u526a\u3002\r\n- `clip_by_polygon`\uff1a\u7528\u4efb\u610f\u591a\u8fb9\u5f62\u88c1\u526a\u3002\r\n\r\n\u7528\u56fd\u754c\u88c1\u526a `contourf` \u7684\u4f8b\u5b50\uff1a\r\n\r\n```python\r\nimport matplotlib.pyplot as plt\r\nimport frykit.plot as fplt\r\n\r\nax = plt.axes(projection=fplt.PLATE_CARREE)\r\nfplt.add_cn_province(ax)\r\nfplt.add_cn_line(ax)\r\n\r\ndata = fplt.load_test_data()\r\ncf = ax.contourf(\r\n    data.lon,\r\n    data.lat,\r\n    data.t2m,\r\n    levels=20,\r\n    cmap='rainbow',\r\n    transform=fplt.PLATE_CARREE,\r\n)\r\nfplt.clip_by_cn_border(cf)\r\n\r\nplt.show()\r\n```\r\n\r\n![clip_by_cn_border](image/clip_by_cn_border.png)\r\n\r\n\u591a\u7701\u88c1\u526a\u76f4\u63a5\u4f20\u5165\u5217\u8868\u5373\u53ef\uff1a\r\n\r\n```python\r\nfplt.clip_by_cn_province(artist, ['\u5317\u4eac\u5e02', '\u5929\u6d25\u5e02', '\u6cb3\u5317\u7701'])\r\n```\r\n\r\n\u66f4\u590d\u6742\u7684\u88c1\u526a\u9700\u8981\u624b\u52a8\u5904\u7406\u591a\u8fb9\u5f62\uff1a\r\n\r\n```python\r\n\u5317\u4eac\u5e02 = fshp.get_cn_province('\u5317\u4eac\u5e02')\r\n\u4fdd\u5b9a\u5e02 = fshp.get_cn_city('\u4fdd\u5b9a\u5e02')\r\n\u5f20\u5bb6\u53e3\u5e02 = fshp.get_cn_city('\u5f20\u5bb6\u53e3\u5e02')\r\npolygon = shapely.union_all([\u5317\u4eac\u5e02, \u4fdd\u5b9a\u5e02, \u5f20\u5bb6\u53e3\u5e02])\r\nfplt.clip_by_polygon(artist, polygon)\r\n```\r\n\r\n### \u5236\u4f5c\u63a9\u819c\r\n\r\n\u88c1\u526a\u662f\u5728\u753b\u56fe\u9636\u6bb5\u4ece\u89c6\u89c9\u6548\u679c\u4e0a\u5c4f\u853d\u591a\u8fb9\u5f62\u5916\u7684\u6570\u636e\uff0c\u800c\u63a9\u819c\u5219\u662f\u5728\u6570\u636e\u5904\u7406\u9636\u6bb5\u5bf9\u591a\u8fb9\u5f62\u5916\u7684\u6570\u636e\u8fdb\u884c\u5904\u7406\uff0c\u4f8b\u5982\u8bbe\u4e3a\u7f3a\u6d4b\u3002\r\n\r\n```python\r\nborder = fshp.get_cn_border()\r\nmask = fshp.polygon_mask(border, lon2d, lat2d)\r\ndata[~mask] = np.nan\r\nax.contourf(lon2d, lat2d, data)\r\n```\r\n\r\n\u5982\u679c\u6570\u636e\u5750\u6807\u80fd\u7528\u4e8c\u7ef4\u76f4\u7ebf\u7f51\u683c\u63cf\u8ff0\uff0c\u90a3\u4e48\u8fd8\u63d0\u4f9b\u66f4\u4f18\u5316\u7684 `polygon_mask2` \u51fd\u6570\uff1a\r\n\r\n```python\r\nmask = fshp.polygon_mask2(border, lon1d, lat1d)\r\ndata[~mask] = np.nan\r\n```\r\n\r\n### \u8bbe\u7f6e\u5730\u56fe\u8303\u56f4\u548c\u523b\u5ea6\r\n\r\n`GeoAxes` \u8bbe\u7f6e\u5730\u56fe\u8303\u56f4\u548c\u523b\u5ea6\u9700\u8981\u4ee5\u4e0b\u6b65\u9aa4\uff1a\r\n\r\n```python\r\nimport numpy as np\r\nimport cartopy.crs as ccrs\r\nfrom cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter\r\n\r\ncrs = ccrs.PlateCarree()\r\nax.set_extent((70, 140, 0, 60), crs=crs)\r\nax.set_xticks(np.arange(70, 141, 10), crs=crs)\r\nax.set_yticks(np.arange(0, 61, 10), crs=crs)\r\nax.xaxis.set_major_formatter(LongitudeFormatter())\r\nax.yaxis.set_major_formatter(LatitudeFormatter())\r\n```\r\n\r\n`set_map_ticks` \u51fd\u6570\u53ef\u4ee5\u5c06\u8fd9\u6bb5\u7b80\u5316\u6210\u4e00\u884c\uff1a\r\n\r\n```python\r\nfplt.set_map_ticks(ax, (70, 140, 0, 60), dx=10, dy=10)\r\n```\r\n\r\n\u4f1a\u81ea\u52a8\u6839\u636e\u7ecf\u5ea6\u95f4\u9694\u548c\u7eac\u5ea6\u95f4\u9694\u751f\u6210\u523b\u5ea6\uff0c\u5e76\u52a0\u4e0a\u5ea6\u6570\u548c\u4e1c\u5357\u897f\u5317\u7684\u7b26\u53f7\u3002\u53e6\u5916\u8fd8\u53ef\u4ee5\uff1a\r\n\r\n* \u7528 `xticks` \u548c `yticks` \u663e\u5f0f\u6307\u5b9a\u523b\u5ea6\u3002\r\n* \u7528 `mx` \u548c `my` \u53c2\u6570\u6307\u5b9a\u6b21\u523b\u5ea6\u7684\u6570\u91cf\u3002\r\n* \u9002\u7528\u4e8e\u975e\u7b49\u7ecf\u7eac\u5ea6\u6295\u5f71\u3002\r\n\r\n> \u5bf9\u4e8e\u975e\u7b49\u7ecf\u7eac\u5ea6\u6295\u5f71\u7684 `GeoAxes`\uff0c\u5982\u679c\u663e\u793a\u8303\u56f4\u4e0d\u662f\u77e9\u5f62\uff0c\u6216\u8005\u8303\u56f4\u8de8\u8d8a\u4e86 180 \u5ea6\u7ecf\u7ebf\uff0c\u8be5\u51fd\u6570\u53ef\u80fd\u4ea7\u751f\u9519\u8bef\u7684\u6548\u679c\u3002\r\n\r\n### \u6dfb\u52a0\u98ce\u77e2\u91cf\u56fe\u4f8b\r\n\r\n\u5728\u53f3\u4e0b\u89d2\u6dfb\u52a0\u4e00\u4e2a\u767d\u8272\u77e9\u5f62\u80cc\u666f\u7684\u98ce\u77e2\u91cf\u56fe\u4f8b\uff1a\r\n\r\n```python\r\nQ = ax.quiver(x, y, u, v, transform=fplt.PLATE_CARREE)\r\nfplt.add_quiver_legend(Q, U=10, width=0.15, height=0.12)\r\n```\r\n\r\n### \u6dfb\u52a0\u6307\u5317\u9488\r\n\r\n```python\r\nfplt.add_compass(ax, 0.95, 0.8, size=15)\r\n```\r\n\r\n\u6307\u5317\u9488\u7684\u4f4d\u7f6e\u57fa\u4e8e `Axes` \u5750\u6807\u7cfb\u3002 `ax` \u662f `GeoAxes` \u65f6\u6307\u5317\u9488\u4f1a\u81ea\u52a8\u6307\u5411\u6240\u5728\u4f4d\u7f6e\u5904\u7684\u5317\u5411\uff0c\u4e5f\u53ef\u4ee5\u901a\u8fc7 `angle` \u53c2\u6570\u624b\u52a8\u6307\u5b9a\u89d2\u5ea6\u3002\r\n\r\n### \u6dfb\u52a0\u6bd4\u4f8b\u5c3a\r\n\r\n```python\r\nscale_bar = fplt.add_scale_bar(ax, 0.36, 0.8, length=1000)\r\nscale_bar.set_xticks([0, 500, 1000])\r\n```\r\n\r\n\u6bd4\u4f8b\u5c3a\u7684\u957f\u5ea6\u901a\u8fc7\u91c7\u6837 `GeoAxes` \u4e2d\u5fc3\u5904\u5355\u4f4d\u957f\u5ea6\u5bf9\u5e94\u7684\u5730\u7406\u8ddd\u79bb\u5f97\u51fa\u3002\u6bd4\u4f8b\u5c3a\u5bf9\u8c61\u7c7b\u4f3c `Axes`\uff0c\u53ef\u4ee5\u7528 `set_xticks` \u7b49\u65b9\u6cd5\u8fdb\u4e00\u6b65\u4fee\u6539\u6837\u5f0f\u3002\r\n\r\n### \u6dfb\u52a0\u5c0f\u5730\u56fe\r\n\r\n```python\r\nmini_ax = fplt.add_mini_axes(ax)\r\nmini_ax.set_extent((105, 120, 2, 25), crs=fplt.PLATE_CARREE)\r\nfplt.add_cn_province(mini_ax)\r\nfplt.add_cn_line(mini_ax)\r\n```\r\n\r\n\u5c0f\u5730\u56fe\u9ed8\u8ba4\u4f7f\u7528\u5927\u5730\u56fe\u7684\u6295\u5f71\uff0c\u4f1a\u81ea\u52a8\u5b9a\u4f4d\u5230\u5927\u5730\u56fe\u7684\u89d2\u843d\uff0c\u65e0\u9700\u50cf `add_axes` \u90a3\u6837\u9700\u8981\u53cd\u590d\u8c03\u6574\u4f4d\u7f6e\u3002\r\n\r\n### GMT \u98ce\u683c\u8fb9\u6846\r\n\r\n```python\r\nfplt.add_frame(ax)\r\n```\r\n\r\n\u6dfb\u52a0\u7c7b\u4f3c [GMT](https://www.generic-mapping-tools.org/) \u98ce\u683c\u7684\u9ed1\u767d\u76f8\u95f4\u683c\u5b50\u7684\u8fb9\u6846\u3002\u76ee\u524d\u4ec5\u652f\u6301\u7b49\u7ecf\u7eac\u5ea6\u6216\u58a8\u5361\u6258\u6295\u5f71\u7684 `GeoAxes`\u3002\r\n\r\n\u4e5f\u53ef\u4ee5\u7528\u6765\u5236\u4f5c GMT \u98ce\u683c\u7684\u6bd4\u4f8b\u5c3a\uff1a\r\n\r\n```python\r\nfplt.add_frame(scale_bar)\r\n```\r\n\r\n### \u7279\u6b8a colorbar\r\n\r\n\u6784\u9020\u4e00\u4e2a\u989c\u8272\u5bf9\u5e94\u4e00\u4e2a\u523b\u5ea6\u7684 colorbar\uff1a\r\n\r\n```python\r\ncolors = [\r\n    'orangered',\r\n    'orange',\r\n    'yellow',\r\n    'limegreen',\r\n    'royalblue',\r\n    'darkviolet'\r\n]\r\ncmap, norm, ticks = fplt.make_qualitative_palette(colors)\r\ncbar = fplt.plot_colormap(cmap, norm)\r\ncbar.set_ticks(ticks)\r\ncbar.set_ticklabels(colors)\r\n```\r\n\r\n\u6784\u9020\u96f6\u503c\u6240\u5728\u533a\u95f4\u5bf9\u5e94\u767d\u8272\u7684 colorbar\uff1a\r\n\r\n```python\r\nimport cmaps\r\n\r\nboundaries = [-10, -5, -2, -1, 1, 2, 5, 10, 20, 50, 100]\r\nnorm = fplt.CenteredBoundaryNorm(boundaries)\r\ncbar = fplt.plot_colormap(cmaps.BlueWhiteOrangeRed, norm)\r\ncbar.set_ticks(boundaries)\r\n```\r\n\r\n![colorbar](image/colorbar.png)\r\n\r\n## \u6027\u80fd\u6d4b\u8bd5\r\n\r\nCartopy \u548c frykit \u7ed8\u5236\u884c\u653f\u533a\u5212\u7684\u8017\u65f6\u5982\u4e0b\u8868\u6240\u793a\uff1a\r\n\r\n<table><thead><tr><th rowspan=\"2\">\u8303\u56f4</th><th rowspan=\"2\">\u533a\u5212</th><th colspan=\"3\">cartopy</th><th colspan=\"3\">frykit</th></tr><tr><th>1</th><th>2</th><th>3</th><th>1</th><th>2</th><th>3</th></tr></thead><tbody><tr><td rowspan=\"4\">\u5168\u56fd</td><td>\u56fd</td><td>3.48</td><td>0.16</td><td>0.18</td><td>0.4</td><td>0.2</td><td>0.16</td></tr><tr><td>\u7701</td><td>12.43</td><td>0.32</td><td>0.28</td><td>0.75</td><td>0.26</td><td>0.25</td></tr><tr><td>\u5e02</td><td>31.54</td><td>0.48</td><td>0.48</td><td>1.61</td><td>0.5</td><td>0.48</td></tr><tr><td>\u53bf</td><td>73.05</td><td>1.0</td><td>0.93</td><td>3.71</td><td>1.03</td><td>0.95</td></tr><tr><td rowspan=\"4\">\u5357\u65b9</td><td>\u56fd</td><td>3.16</td><td>0.14</td><td>0.15</td><td>0.33</td><td>0.14</td><td>0.2</td></tr><tr><td>\u7701</td><td>11.85</td><td>0.17</td><td>0.16</td><td>0.29</td><td>0.14</td><td>0.15</td></tr><tr><td>\u5e02</td><td>31.35</td><td>0.2</td><td>0.22</td><td>0.46</td><td>0.16</td><td>0.16</td></tr><tr><td>\u53bf</td><td>76.59</td><td>0.34</td><td>0.33</td><td>0.77</td><td>0.24</td><td>0.24</td></tr></tbody></table>\r\n\r\n```\r\n# \u73af\u5883\u7248\u672c\r\npython==3.13.5\r\ncartopy==0.25.0\r\nfrykit==0.7.6\r\n```\r\n\r\n- \u4f7f\u7528\u9ad8\u5fb7\u6570\u636e\r\n- \u4f7f\u7528\u7b49\u8ddd\u65b9\u4f4d\u6295\u5f71\uff0c\u4ee5\u4f53\u73b0\u76f4\u63a5\u7528 pyproj \u548c\u7528 Cartopoy \u505a\u6295\u5f71\u7684\u65f6\u95f4\u5dee\u5f02\u3002\r\n- `cartopy>=0.23` \u65f6\u5373\u4fbf `GeoAxes` \u7684\u8303\u56f4\u5f88\u5c0f\uff0c\u4ecd\u9700\u8981\u5bf9\u8303\u56f4\u5916\u7684\u6240\u6709\u591a\u8fb9\u5f62\u505a\u6295\u5f71\uff0c\u5bfc\u81f4\u901f\u5ea6\u5f88\u6162\u3002\u800c frykit \u5bf9\u6b64\u6709\u4f18\u5316\u3002\u56e0\u6b64\u8fd9\u91cc\u8bbe\u7f6e\u4e24\u79cd\u5730\u56fe\u8303\u56f4\uff1a\u5168\u56fd `(70, 140, 0, 60)` \u548c\u5357\u65b9 `(115, 120, 24, 28)`\u3002\r\n- \u6bd4\u8f83\u8fde\u7eed\u753b\u4e09\u5f20\u56fe\u7684\u7ed3\u679c\uff0c\u7f13\u5b58\u673a\u5236\u4f1a\u4f7f\u7b2c\u4e00\u6b21\u8017\u65f6\u6700\u957f\uff0c\u540e\u7eed\u8017\u65f6\u5927\u5e45\u7f29\u77ed\u3002\r\n\r\n\u6d4b\u8bd5\u811a\u672c\u89c1 [script/measure_time.py](script/measure_time.py)\r\n\r\n## \u8be6\u7ec6\u4ecb\u7ecd\r\n\r\n\u5de5\u5177\u7bb1\u7684\u539f\u7406\u548c\u4f7f\u7528\u573a\u666f\u53ef\u89c1\u4e0b\u9762\u51e0\u7bc7\u535a\u6587\uff1a\r\n\r\n- [Cartopy \u7cfb\u5217\uff1a\u753b\u4e2d\u56fd\u5730\u56fe\u7684\u5de5\u5177\u7bb1 frykit](https://zhajiman.github.io/post/frykit/)\r\n- [Cartopy \u7cfb\u5217\uff1a\u63a2\u7d22 shapefile](https://zhajiman.github.io/post/cartopy_shapefile/)\r\n- [Cartopy \u7cfb\u5217\uff1a\u88c1\u526a\u586b\u8272\u56fe\u51fa\u754c\u95ee\u9898](https://zhajiman.github.io/post/cartopy_clip_outside/)\r\n- [Cartopy \u6dfb\u52a0\u5357\u6d77\u5c0f\u5730\u56fe\u7684\u4e09\u79cd\u65b9\u6cd5](https://mp.weixin.qq.com/s/-QMVN6MS-UuQ9lQjz9vqBQ)\r\n- [Matplotlib \u7cfb\u5217\uff1acolormap \u7684\u8bbe\u7f6e](https://zhajiman.github.io/post/matplotlib_colormap/)\r\n- [\u5929\u5730\u56fe\u201c\u5e26\u5ba1\u56fe\u53f7\u201d\u7684\u884c\u653f\u533a\u5212\u6570\u636e](http://bbs.06climate.com/forum.php?mod=viewthread&tid=109893)\r\n\r\n## \u793a\u4f8b\u6548\u679c\r\n\r\n\u4ed3\u5e93\u7684 `example` \u76ee\u5f55\u91cc\u6709\u66f4\u590d\u6742\u7684\u793a\u4f8b\u811a\u672c\uff1a\r\n\r\n- [\u5728\u666e\u901a `Axes` \u4e0a\u753b\u5730\u56fe](example/axes.py)\r\n\r\n![axes](image/axes.png)\r\n\r\n- [\u5206\u7701\u586b\u8272](example/fill.py)\r\n\r\n![fill](image/fill.png)\r\n\r\n- [\u88c1\u526a `contourf` \u548c `quiver`](example/quiver.py)\r\n\r\n![quiver](image/quiver.png)\r\n\r\n- [\u88c1\u526a\u51fa\u754c\u5904\u7406](example/strict_clip.py)\r\n\r\n![strict_clip](image/strict_clip.png)\r\n\r\n- [\u88c1\u526a\u4e3b\u56fe\u548c\u5357\u6d77\u5c0f\u56fe\u7684 `contourf`](example/contourf.py)\r\n\r\n![contourf](image/contourf.png)\r\n\r\n- [\u6a21\u4eff NERV \u98ce\u683c\u7684\u5730\u56fe](example/nerv_style.py)\r\n\r\n![nerv_style](image/nerv_style.png)\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A simple toolbox for Matplotib and Cartopy",
    "version": "0.7.6",
    "project_urls": {
        "Changelog": "https://github.com/ZhaJiMan/frykit/blob/main/CHANGELOG.md",
        "Homepage": "https://github.com/ZhaJiMan/frykit",
        "Issues": "https://github.com/ZhaJiMan/frykit/issues"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "41e453d1ad4f71f4a25c9547a9eda89f62e4b25467956898643b49d78c4f17ca",
                "md5": "9c24048378f9d3a33dbf4cb13995c8e2",
                "sha256": "b6233dd18ac8aae97095b97f0fd8c09848f2099192b2ddb1cdd8841f7f676ebb"
            },
            "downloads": -1,
            "filename": "frykit-0.7.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9c24048378f9d3a33dbf4cb13995c8e2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 61157,
            "upload_time": "2025-10-10T09:50:29",
            "upload_time_iso_8601": "2025-10-10T09:50:29.320562Z",
            "url": "https://files.pythonhosted.org/packages/41/e4/53d1ad4f71f4a25c9547a9eda89f62e4b25467956898643b49d78c4f17ca/frykit-0.7.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ef42794d0ae6093b2c1b832cce8f7538366aff7dc4eea3fb0a1e336af4da5967",
                "md5": "0a1a9b1b509faaed0358a271104a69ef",
                "sha256": "d3f6878a25c953e227b3d7d79f5c856fc8dc828b5627721716d17151db05ae48"
            },
            "downloads": -1,
            "filename": "frykit-0.7.6.tar.gz",
            "has_sig": false,
            "md5_digest": "0a1a9b1b509faaed0358a271104a69ef",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 62248,
            "upload_time": "2025-10-10T09:50:30",
            "upload_time_iso_8601": "2025-10-10T09:50:30.967346Z",
            "url": "https://files.pythonhosted.org/packages/ef/42/794d0ae6093b2c1b832cce8f7538366aff7dc4eea3fb0a1e336af4da5967/frykit-0.7.6.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-10 09:50:30",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ZhaJiMan",
    "github_project": "frykit",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "frykit"
}
        
Elapsed time: 1.62061s