爱气象,爱气象家园! 

气象家园

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

搜索
查看: 1073|回复: 0

[经验总结] Matplotlib 绘制指北针,但是自动决定方向

[复制链接]

新浪微博达人勋

发表于 2023-11-18 12:58:57 | 显示全部楼层 |阅读模式

登录后查看更多精彩内容~

您需要 登录 才可以下载或查看,没有帐号?立即注册 新浪微博登陆

x
本帖最后由 灭火器 于 2023-11-20 23:08 编辑

指北针的箭头可以用一黑一白两个三角形拼在一起来模拟,再在箭头尖端标注 N 字。

首先确定两个三角形的大小:以箭头的凹陷处为 (0, 0),设箭头初始指向 x 轴正方向,那么上半部分三角形顶点的坐标可以设为 [(0, 0), (2, 0), (-1, 1), (0, 0)],下半部分根据对称性如法炮制。然后用这些顶点构造出两个 matplotlib 的 Path 对象,再打包构造成 PathCollection,就得到了一个代表箭头的 Artist 对象。

我希望箭头的大小比例不受 Axes 或 GeoAxes 的影响,所以选择了跟文字一样的单位:point。同时希望指北针的横纵坐标用 Axes 坐标表示,这样的话 add_compass(ax, 0.5, 0.5) 就能在地图正中间放置指北针。因此为 PathCollection 设计这样的 Transform:

  1. trans = ax.figure.dpi_scale_trans + rotation + translation
复制代码
第一项表示物理坐标系;第二项是 Affine2D().rotate_deg(angle),决定指北针的角度;第三项是 ScaledTranslation,能将 (0, 0) 处的指北针平移到 Axes 坐标系里 (x, y) 的位置。

指北针的角度可以手动指定,也可以从地图上计算得来,方法是先将 (x, y) 换算成 (lon, lat),再计算出 data 坐标系中 (lon, lat) 和 (lon, lat + 1) 两点间连线的角度,最后将这个角度直接用于物理坐标系。

效果如下面两图所示 global.png regional.png
具体代码为:
  1. import math
  2. from matplotlib.path import Path
  3. from matplotlib.collections import PathCollection
  4. from matplotlib.transforms import Affine2D, ScaledTranslation
  5. from cartopy.crs import PlateCarree
  6. from cartopy.mpl.geoaxes import GeoAxes

  7. def add_compass(ax, x, y, angle=None, size=20):
  8.     '''
  9.     向Axes添加指北针.

  10.     x和y是指北针的横纵坐标, 基于axes坐标系.

  11.     指北针的方向, 从x轴逆时针方向算起, 单位为度.
  12.     当ax是GeoAxes时默认自动计算角度, 否则默认表示90度.

  13.     size是指北针的大小, 单位为点(point).
  14.     '''
  15.     if angle is None:
  16.         if isinstance(ax, GeoAxes):
  17.             crs = PlateCarree()
  18.             axes_to_data = ax.transAxes - ax.transData
  19.             x0, y0 = axes_to_data.transform((x, y))
  20.             lon0, lat0 = crs.transform_point(x0, y0, ax.projection)
  21.             lon1, lat1 = lon0, min(lat0 + 1, 90)
  22.             x1, y1 = ax.projection.transform_point(lon1, lat1, crs)
  23.             angle = math.degrees(math.atan2(y1 - y0, x1 - x0))
  24.         else:
  25.             angle = 90

  26.     rotation = Affine2D().rotate_deg(angle)
  27.     translation = ScaledTranslation(x, y, ax.transAxes)
  28.     trans = ax.figure.dpi_scale_trans + rotation + translation

  29.     head = size / 72
  30.     width = axis = head * 2 / 3
  31.     verts1 = [(0, 0), (axis, 0), (axis - head, width / 2), (0, 0)]
  32.     verts2 = [(0, 0), (axis - head, -width / 2), (axis, 0), (0, 0)]
  33.     paths = [Path(verts1), Path(verts2)]
  34.     pc = PathCollection(
  35.         paths,
  36.         facecolors=['k', 'w'],
  37.         edgecolors='k',
  38.         linewidths=1,
  39.         zorder=3,
  40.         clip_on=False,
  41.         transform=trans
  42.     )
  43.     ax.add_collection(pc)

  44.     pad = head / 3
  45.     ax.text(
  46.         axis + pad, 0, 'N',
  47.         fontsize=size / 1.5,
  48.         ha='center', va='center',
  49.         rotation=angle - 90,
  50.         transform=trans
  51.     )
复制代码



密码修改失败请联系微信:mofangbao
您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

Copyright ©2011-2014 bbs.06climate.com All Rights Reserved.  Powered by Discuz! (京ICP-10201084)

本站信息均由会员发表,不代表气象家园立场,禁止在本站发表与国家法律相抵触言论

快速回复 返回顶部 返回列表