8. 装饰器 + Cartopy 地图渲染¶
演示如何用 batch_cmaps 装饰器在 cartopy 地图上,用不同色带渲染真实的温度栅格数据。
导入公共模块¶
本 notebook 需要 cartopy 和 rasterio。如果未安装,代码单元格会跳过执行。
In [1]:
Copied!
import numpy as np
import matplotlib.pyplot as plt
from multicolor import cmap, batch_cmaps, data_ensure
# Check optional dependencies
try:
import cartopy.crs as ccrs
import rasterio
_has_deps = True
except ImportError:
_has_deps = False
print('cartopy or rasterio not installed. Install with: pip install cartopy rasterio')
import numpy as np
import matplotlib.pyplot as plt
from multicolor import cmap, batch_cmaps, data_ensure
# Check optional dependencies
try:
import cartopy.crs as ccrs
import rasterio
_has_deps = True
except ImportError:
_has_deps = False
print('cartopy or rasterio not installed. Install with: pip install cartopy rasterio')
cartopy or rasterio not installed. Install with: pip install cartopy rasterio
方式一:@batch_cmaps() — 渲染真实温度栅格¶
用不同发散色带渲染同一份温度数据。数据通过 data_ensure() 自动下载。
In [2]:
Copied!
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
# 自动下载并获取本地路径
tavg_path = data_ensure('tavg_10min')
print(f'Data file: {tavg_path}')
# 读取栅格数据和地理范围
with rasterio.open(tavg_path) as src:
temperature = src.read(1)
temperature = np.ma.masked_equal(temperature, src.nodata)
extent = [src.bounds.left, src.bounds.right, src.bounds.bottom, src.bounds.top]
print(f'Shape: {temperature.shape}, dtype: {temperature.dtype}')
# 用不同发散色带渲染
@batch_cmaps(['Coolwarm', 'RdYlGn', 'RdBu'])
def show_temperature_raster(cmap_obj, ax=None):
if ax is None:
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
im = ax.imshow(
temperature,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.5)
ax.set_title(f'Average Temperature ({cmap_obj.name})', fontsize=11)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
show_temperature_raster()
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
# 自动下载并获取本地路径
tavg_path = data_ensure('tavg_10min')
print(f'Data file: {tavg_path}')
# 读取栅格数据和地理范围
with rasterio.open(tavg_path) as src:
temperature = src.read(1)
temperature = np.ma.masked_equal(temperature, src.nodata)
extent = [src.bounds.left, src.bounds.right, src.bounds.bottom, src.bounds.top]
print(f'Shape: {temperature.shape}, dtype: {temperature.dtype}')
# 用不同发散色带渲染
@batch_cmaps(['Coolwarm', 'RdYlGn', 'RdBu'])
def show_temperature_raster(cmap_obj, ax=None):
if ax is None:
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
im = ax.imshow(
temperature,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.5)
ax.set_title(f'Average Temperature ({cmap_obj.name})', fontsize=11)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
show_temperature_raster()
Skipping: requires cartopy and rasterio
方式二:@batch_cmaps() — 按查询 kwargs 渲染¶
直接传入筛选条件,不用手动 cmap.names()。
In [3]:
Copied!
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
# 用 sequential 类型色带渲染最高气温数据
tmax_path = data_ensure('tmax_10min')
with rasterio.open(tmax_path) as src:
tmax_data = src.read(1)
tmax_data = np.ma.masked_equal(tmax_data, src.nodata)
extent = [src.bounds.left, src.bounds.right, src.bounds.bottom, src.bounds.top]
@batch_cmaps(cmap_type='sequential', colorblind_safe='any', limit=3)
def show_tmax_raster(cmap_obj, ax=None):
if ax is None:
fig = plt.figure(figsize=(8, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
im = ax.imshow(
tmax_data,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.5)
ax.set_title(f'Max Temperature ({cmap_obj.name})', fontsize=11)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
show_tmax_raster()
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
# 用 sequential 类型色带渲染最高气温数据
tmax_path = data_ensure('tmax_10min')
with rasterio.open(tmax_path) as src:
tmax_data = src.read(1)
tmax_data = np.ma.masked_equal(tmax_data, src.nodata)
extent = [src.bounds.left, src.bounds.right, src.bounds.bottom, src.bounds.top]
@batch_cmaps(cmap_type='sequential', colorblind_safe='any', limit=3)
def show_tmax_raster(cmap_obj, ax=None):
if ax is None:
fig = plt.figure(figsize=(8, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
im = ax.imshow(
tmax_data,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.5)
ax.set_title(f'Max Temperature ({cmap_obj.name})', fontsize=11)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
show_tmax_raster()
Skipping: requires cartopy and rasterio
方式三:@batch_cmaps() — 色盲安全色带对比¶
用 query_func 动态获取色盲安全色带,对比它们在真实数据上的效果。
In [4]:
Copied!
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
# 用最低气温数据
tmin_path = data_ensure('tmin_10min')
with rasterio.open(tmin_path) as src:
tmin_data = src.read(1)
tmin_data = np.ma.masked_equal(tmin_data, src.nodata)
extent = [src.bounds.left, src.bounds.right, src.bounds.bottom, src.bounds.top]
@batch_cmaps(
query_func=lambda: cmap.names(colorblind_safe='any', is_custom=False, limit=6),
layout='single',
)
def show_cb_safe_raster(cmap_obj):
fig = plt.figure(figsize=(8, 4))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
im = ax.imshow(
tmin_data,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.3)
ax.set_title(f'Min Temperature ({cmap_obj.name})', fontsize=10)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
show_cb_safe_raster()
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
# 用最低气温数据
tmin_path = data_ensure('tmin_10min')
with rasterio.open(tmin_path) as src:
tmin_data = src.read(1)
tmin_data = np.ma.masked_equal(tmin_data, src.nodata)
extent = [src.bounds.left, src.bounds.right, src.bounds.bottom, src.bounds.top]
@batch_cmaps(
query_func=lambda: cmap.names(colorblind_safe='any', is_custom=False, limit=6),
layout='single',
)
def show_cb_safe_raster(cmap_obj):
fig = plt.figure(figsize=(8, 4))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
im = ax.imshow(
tmin_data,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.3)
ax.set_title(f'Min Temperature ({cmap_obj.name})', fontsize=10)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
show_cb_safe_raster()
Skipping: requires cartopy and rasterio
方式四:@batch_cmaps() — 按 ID 渲染站点散点图¶
从数据库获取色带 ID,在地图上渲染模拟站点数据。
In [5]:
Copied!
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
np.random.seed(42)
n_stations = 50
station_lons = np.random.uniform(-180, 180, n_stations)
station_lats = np.random.uniform(-60, 60, n_stations)
station_values = np.random.uniform(-10, 40, n_stations)
# 获取前 3 个色带的 ID
items = cmap.list(limit=3)
print(f'IDs: {[item[0] for item in items]}')
print(f'Names: {[item[1] for item in items]}')
@batch_cmaps([item[0] for item in items])
def show_station_scatter(cmap_obj, ax=None):
if ax is None:
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
sc = ax.scatter(
station_lons,
station_lats,
c=station_values,
cmap=cmap_obj,
s=60,
transform=ccrs.PlateCarree(),
edgecolors='white',
linewidth=0.5,
vmin=-10,
vmax=40,
)
ax.coastlines(linewidth=0.5)
ax.set_title(f'Station Temperature ({cmap_obj.name})', fontsize=11)
plt.colorbar(
sc, ax=ax, orientation='horizontal', pad=0.05, label='Temperature'
)
show_station_scatter()
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
np.random.seed(42)
n_stations = 50
station_lons = np.random.uniform(-180, 180, n_stations)
station_lats = np.random.uniform(-60, 60, n_stations)
station_values = np.random.uniform(-10, 40, n_stations)
# 获取前 3 个色带的 ID
items = cmap.list(limit=3)
print(f'IDs: {[item[0] for item in items]}')
print(f'Names: {[item[1] for item in items]}')
@batch_cmaps([item[0] for item in items])
def show_station_scatter(cmap_obj, ax=None):
if ax is None:
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
sc = ax.scatter(
station_lons,
station_lats,
c=station_values,
cmap=cmap_obj,
s=60,
transform=ccrs.PlateCarree(),
edgecolors='white',
linewidth=0.5,
vmin=-10,
vmax=40,
)
ax.coastlines(linewidth=0.5)
ax.set_title(f'Station Temperature ({cmap_obj.name})', fontsize=11)
plt.colorbar(
sc, ax=ax, orientation='horizontal', pad=0.05, label='Temperature'
)
show_station_scatter()
Skipping: requires cartopy and rasterio
方式五:@batch_cmaps() — grid 模式多子图¶
将不同色带画在一张多子图表格上,方便对比。
In [6]:
Copied!
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
@batch_cmaps(
['Viridis', 'Plasma', 'Inferno', 'Cividis'],
layout='grid',
cols=2,
)
def show_grid_raster(cmap_obj, ax=None):
im = ax.imshow(
temperature,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.3)
ax.set_title(cmap_obj.name, fontsize=10)
ax.set_global()
show_grid_raster()
if not _has_deps:
print('Skipping: requires cartopy and rasterio')
else:
@batch_cmaps(
['Viridis', 'Plasma', 'Inferno', 'Cividis'],
layout='grid',
cols=2,
)
def show_grid_raster(cmap_obj, ax=None):
im = ax.imshow(
temperature,
cmap=cmap_obj,
extent=extent,
transform=ccrs.PlateCarree(),
)
ax.coastlines(linewidth=0.3)
ax.set_title(cmap_obj.name, fontsize=10)
ax.set_global()
show_grid_raster()
Skipping: requires cartopy and rasterio