oafuncs 0.0.85__tar.gz → 0.0.87__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {oafuncs-0.0.85/oafuncs.egg-info → oafuncs-0.0.87}/PKG-INFO +3 -3
  2. oafuncs-0.0.87/oafuncs/data_store/OAFuncs.png +0 -0
  3. oafuncs-0.0.87/oafuncs/oa_cmap.py +213 -0
  4. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_data.py +88 -4
  5. oafuncs-0.0.87/oafuncs/oa_draw.py +325 -0
  6. oafuncs-0.0.87/oafuncs/oa_help.py +137 -0
  7. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_nc.py +134 -86
  8. {oafuncs-0.0.85 → oafuncs-0.0.87/oafuncs.egg-info}/PKG-INFO +3 -3
  9. {oafuncs-0.0.85 → oafuncs-0.0.87}/setup.py +3 -10
  10. oafuncs-0.0.85/oafuncs/data_store/OAFuncs.png +0 -0
  11. oafuncs-0.0.85/oafuncs/oa_cmap.py +0 -164
  12. oafuncs-0.0.85/oafuncs/oa_draw.py +0 -513
  13. oafuncs-0.0.85/oafuncs/oa_help.py +0 -42
  14. {oafuncs-0.0.85 → oafuncs-0.0.87}/LICENSE.txt +0 -0
  15. {oafuncs-0.0.85 → oafuncs-0.0.87}/MANIFEST.in +0 -0
  16. {oafuncs-0.0.85 → oafuncs-0.0.87}/README.md +0 -0
  17. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/__init__.py +0 -0
  18. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_down/User_Agent-list.txt +0 -0
  19. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_down/__init__.py +0 -0
  20. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_down/hycom_3hourly.py +0 -0
  21. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_down/literature.py +0 -0
  22. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_down/test_ua.py +0 -0
  23. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_down/user_agent.py +0 -0
  24. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_file.py +0 -0
  25. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_python.py +0 -0
  26. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_sign/__init__.py +0 -0
  27. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_sign/meteorological.py +0 -0
  28. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_sign/ocean.py +0 -0
  29. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_sign/scientific.py +0 -0
  30. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_tool/__init__.py +0 -0
  31. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs/oa_tool/email.py +0 -0
  32. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs.egg-info/SOURCES.txt +0 -0
  33. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs.egg-info/dependency_links.txt +0 -0
  34. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs.egg-info/requires.txt +0 -0
  35. {oafuncs-0.0.85 → oafuncs-0.0.87}/oafuncs.egg-info/top_level.txt +0 -0
  36. {oafuncs-0.0.85 → oafuncs-0.0.87}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: oafuncs
3
- Version: 0.0.85
4
- Summary: My short description for my project.
3
+ Version: 0.0.87
4
+ Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
7
7
  Author-email: liukun0312@stu.ouc.edu.cn
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: Implementation :: CPython
17
17
  Classifier: Programming Language :: Python :: Implementation :: PyPy
18
- Requires-Python: >=3.9.0
18
+ Requires-Python: >=3.7.0
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  Requires-Dist: matplotlib
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """
4
+ Author: Liu Kun && 16031215@qq.com
5
+ Date: 2024-09-17 16:55:11
6
+ LastEditors: Liu Kun && 16031215@qq.com
7
+ LastEditTime: 2024-11-21 13:14:24
8
+ FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_cmap.py
9
+ Description:
10
+ EditPlatform: vscode
11
+ ComputerInfo: XPS 15 9510
12
+ SystemInfo: Windows 11
13
+ Python Version: 3.11
14
+ """
15
+
16
+ import matplotlib as mpl
17
+ import matplotlib.pyplot as plt
18
+ import numpy as np
19
+ from rich import print
20
+
21
+ __all__ = ["show", "to_color", "create", "create_rgbtxt", "get"]
22
+
23
+ # ** 将cmap用填色图可视化(官网摘抄函数)
24
+ def show(colormaps):
25
+ """
26
+ Description:
27
+ Helper function to plot data with associated colormap.
28
+ Parameters:
29
+ colormaps : list of colormaps, or a single colormap; can be a string or a colormap object.
30
+ Example:
31
+ cmap = ListedColormap(["darkorange", "gold", "lawngreen", "lightseagreen"])
32
+ show([cmap]); show("viridis"); show(["viridis", "cividis"])
33
+ """
34
+ if isinstance(colormaps, str) or isinstance(colormaps, mpl.colors.Colormap):
35
+ colormaps = [colormaps]
36
+ np.random.seed(19680801)
37
+ data = np.random.randn(30, 30)
38
+ n = len(colormaps)
39
+ fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3), constrained_layout=True, squeeze=False)
40
+ for [ax, cmap] in zip(axs.flat, colormaps):
41
+ psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
42
+ fig.colorbar(psm, ax=ax)
43
+ plt.show()
44
+
45
+
46
+ # ** 将cmap转为list,即多个颜色的列表
47
+ def to_color(cmap, n=256):
48
+ """
49
+ Description:
50
+ Convert a colormap to a list of colors
51
+ Parameters:
52
+ cmap : str; the name of the colormap
53
+ n : int, optional; the number of colors
54
+ Return:
55
+ out_colors : list of colors
56
+ Example:
57
+ out_colors = to_color('viridis', 256)
58
+ """
59
+ c_map = mpl.colormaps.get_cmap(cmap)
60
+ out_colors = [c_map(i) for i in np.linspace(0, 1, n)]
61
+ return out_colors
62
+
63
+
64
+ # ** 自制cmap,多色,可带位置
65
+ def create(colors: list, nodes=None, under=None, over=None): # 利用颜色快速配色
66
+ """
67
+ Description:
68
+ Create a custom colormap
69
+ Parameters:
70
+ colors : list of colors
71
+ nodes : list of positions
72
+ under : color
73
+ over : color
74
+ Return:
75
+ cmap : colormap
76
+ Example:
77
+ cmap = create(['#C2B7F3','#B3BBF2','#B0CBF1','#ACDCF0','#A8EEED'])
78
+ cmap = create(['aliceblue','skyblue','deepskyblue'],[0.0,0.5,1.0])
79
+ """
80
+
81
+ if nodes is None: # 采取自动分配比例
82
+ cmap_color = mpl.colors.LinearSegmentedColormap.from_list("mycmap", colors)
83
+ else: # 按照提供比例分配
84
+ cmap_color = mpl.colors.LinearSegmentedColormap.from_list("mycmap", list(zip(nodes, colors)))
85
+ if under is not None:
86
+ cmap_color.set_under(under)
87
+ if over is not None:
88
+ cmap_color.set_over(over)
89
+ return cmap_color
90
+
91
+
92
+ # ** 根据RGB的txt文档制作色卡(利用Grads调色盘)
93
+ def create_rgbtxt(rgbtxt_file,split_mark=','): # 根据RGB的txt文档制作色卡/根据rgb值制作
94
+ """
95
+ Description
96
+ -----------
97
+ Make a color card according to the RGB txt document, each line in the txt file is an RGB value, separated by commas, such as: 251,251,253
98
+
99
+ Parameters
100
+ ----------
101
+ rgbtxt_file : str, the path of txt file
102
+ split_mark : str, optional, default is ','; the split mark of rgb value
103
+
104
+ Returns
105
+ -------
106
+ cmap : colormap
107
+
108
+ Example
109
+ -------
110
+ cmap=create_rgbtxt(path,split_mark=',')
111
+
112
+ txt example
113
+ -----------
114
+ 251,251,253
115
+ 225,125,25
116
+ 250,205,255
117
+ """
118
+ with open(rgbtxt_file) as fid:
119
+ data = fid.readlines()
120
+ n = len(data)
121
+ rgb = np.zeros((n, 3))
122
+ for i in np.arange(n):
123
+ rgb[i][0] = data[i].split(split_mark)[0]
124
+ rgb[i][1] = data[i].split(split_mark)[1]
125
+ rgb[i][2] = data[i].split(split_mark)[2]
126
+ max_rgb = np.max(rgb)
127
+ if max_rgb > 2: # if the value is greater than 2, it is normalized to 0-1
128
+ rgb = rgb / 255.0
129
+ my_cmap = mpl.colors.ListedColormap(rgb, name="my_color")
130
+ return my_cmap
131
+
132
+
133
+ # ** 选择cmap
134
+ def get(cmap_name=None, query=False):
135
+ """
136
+ Description:
137
+ Choosing a colormap from the list of available colormaps or a custom colormap
138
+ Parameters:
139
+ cmap_name : str, optional; the name of the colormap
140
+ query : bool, optional; whether to query the available colormap names
141
+ Return:
142
+ cmap : colormap
143
+ Example:
144
+ cmap = get('viridis')
145
+ cmap = get('diverging_1')
146
+ cmap = get('cold_1')
147
+ cmap = get('warm_1')
148
+ cmap = get('colorful_1')
149
+ """
150
+
151
+ my_cmap_dict = {
152
+ "diverging_1": create(["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"]),
153
+ "cold_1": create(["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC"]),
154
+ "warm_1": create(["#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"]),
155
+ # "land_1": create_custom(["#3E6436", "#678A59", "#91A176", "#B8A87D", "#D9CBB2"], under="#A6CEE3", over="#FFFFFF"),
156
+ # "ocean_1": create_custom(["#126697", "#2D88B3", "#4EA1C9", "#78B9D8", "#A6CEE3"], under="#8470FF", over="#3E6436"),
157
+ # "ocean_land_1": create_custom(
158
+ # [
159
+ # "#126697", # 深蓝(深海)
160
+ # "#2D88B3", # 蓝
161
+ # "#4EA1C9", # 蓝绿
162
+ # "#78B9D8", # 浅蓝(浅海)
163
+ # "#A6CEE3", # 浅蓝(近岸)
164
+ # "#AAAAAA", # 灰色(0值,海平面)
165
+ # "#D9CBB2", # 沙质土壤色(陆地开始)
166
+ # "#B8A87D", # 浅棕
167
+ # "#91A176", # 浅绿
168
+ # "#678A59", # 中绿
169
+ # "#3E6436", # 深绿(高山)
170
+ # ]
171
+ # ),
172
+ "colorful_1": create(["#6d00db", "#9800cb", "#F2003C", "#ff4500", "#ff7f00", "#FE28A2", "#FFC0CB", "#DDA0DD", "#40E0D0", "#1a66f2", "#00f7fb", "#8fff88", "#E3FF00"]),
173
+ }
174
+ if query:
175
+ print("Available cmap names:")
176
+ print('-' * 20)
177
+ print('Defined by myself:')
178
+ for key, _ in my_cmap_dict.items():
179
+ print(key)
180
+ print('-' * 20)
181
+ print('Matplotlib built-in:')
182
+ print(mpl.colormaps())
183
+ print("-" * 20)
184
+
185
+ if cmap_name is None:
186
+ return
187
+
188
+ if cmap_name in my_cmap_dict:
189
+ return my_cmap_dict[cmap_name]
190
+ else:
191
+ try:
192
+ return mpl.colormaps.get_cmap(cmap_name)
193
+ except ValueError:
194
+ raise ValueError(f"Unknown cmap name: {cmap_name}")
195
+
196
+
197
+ if __name__ == "__main__":
198
+ # ** 测试自制cmap
199
+ colors = ["#C2B7F3", "#B3BBF2", "#B0CBF1", "#ACDCF0", "#A8EEED"]
200
+ nodes = [0.0, 0.2, 0.4, 0.6, 1.0]
201
+ c_map = create(colors, nodes)
202
+ show([c_map])
203
+
204
+ # ** 测试自制diverging型cmap
205
+ diverging_cmap = create(["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"])
206
+ show([diverging_cmap])
207
+
208
+ # ** 测试根据RGB的txt文档制作色卡
209
+ file_path = "E:/python/colorbar/test.txt"
210
+ cmap_rgb = create_rgbtxt(file_path)
211
+
212
+ # ** 测试将cmap转为list
213
+ out_colors = to_color("viridis", 256)
@@ -15,12 +15,12 @@ Python Version: 3.11
15
15
 
16
16
  import itertools
17
17
  import multiprocessing as mp
18
- from concurrent.futures import ThreadPoolExecutor
18
+ from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
19
19
 
20
20
  import numpy as np
21
21
  from scipy.interpolate import griddata
22
22
 
23
- __all__ = ["interp_2d"]
23
+ __all__ = ["interp_2d","ParallelExecutor"]
24
24
 
25
25
 
26
26
  def interp_2d(target_x, target_y, origin_x, origin_y, data, method="linear", parallel=True):
@@ -90,6 +90,73 @@ def interp_2d(target_x, target_y, origin_x, origin_y, data, method="linear", par
90
90
  return np.array(interpolated_data)
91
91
 
92
92
 
93
+ class ParallelExecutor:
94
+ """
95
+ 通用并行计算类,支持多进程和多线程模式。
96
+
97
+ 使用说明:
98
+ 1. 创建实例时选择模式:
99
+ - mode="process" 使用多进程(适合 CPU 密集型任务)。
100
+ - mode="thread" 使用多线程(适合 IO 密集型任务)。
101
+
102
+ 2. 调用 run 方法:
103
+ - 参数 func:需要并行执行的函数。
104
+ - 参数 param_list:参数列表,每个元素是传递给 func 的参数元组。
105
+
106
+ 示例:
107
+ # 示例 1:计算平方
108
+ def compute_square(x):
109
+ return x * x
110
+
111
+ params = [(i,) for i in range(10)]
112
+ executor = ParallelExecutor(mode="process", max_workers=4)
113
+ results = executor.run(compute_square, params)
114
+ print("Results:", results)
115
+
116
+ # 示例 2:计算两数之和
117
+ def compute_sum(a, b):
118
+ return a + b
119
+
120
+ params = [(1, 2), (3, 4), (5, 6)]
121
+ executor = ParallelExecutor(mode="thread", max_workers=2)
122
+ results = executor.run(compute_sum, params)
123
+ print("Results:", results)
124
+
125
+ 参数:
126
+ mode (str): 并行模式,"process" 表示多进程,"thread" 表示多线程。
127
+ max_workers (int): 最大并行工作数,默认为 CPU 核心数减 2。
128
+ """
129
+
130
+ def __init__(self, mode="process", max_workers=mp.cpu_count() - 2):
131
+ self.mode = mode
132
+ self.max_workers = max_workers
133
+ self.executor = ProcessPoolExecutor if mode == "process" else ThreadPoolExecutor
134
+
135
+ def run(self, func, param_list):
136
+ """
137
+ 并行运行指定函数,并确保结果顺序与输入参数顺序一致。
138
+
139
+ 参数:
140
+ func (callable): 需要并行执行的函数。
141
+ param_list (list): 参数列表,每个元素是传递给 func 的参数元组。
142
+
143
+ 返回:
144
+ results (list): 按输入顺序返回的结果。
145
+ """
146
+ results = [None] * len(param_list) # 预分配结果数组
147
+
148
+ with self.executor(max_workers=self.max_workers) as executor:
149
+ # 提交任务并保存其索引
150
+ future_to_index = {executor.submit(func, *params): idx for idx, params in enumerate(param_list)}
151
+
152
+ for future in future_to_index:
153
+ idx = future_to_index[future] # 获取原始索引
154
+ results[idx] = future.result() # 将结果存放到对应位置
155
+
156
+ return results
157
+
158
+
159
+ # ---------------------------------------------------------------------------------- not used below ----------------------------------------------------------------------------------
93
160
  # ** 高维插值函数,插值最后两个维度
94
161
  def interp_2d_20241213(target_x, target_y, origin_x, origin_y, data, method="linear"):
95
162
  """
@@ -228,8 +295,25 @@ def interp_2d_parallel_20241213(target_x, target_y, origin_x, origin_y, data, me
228
295
  return interpolated_data
229
296
 
230
297
 
298
+ def _test_sum(a,b):
299
+ return a+b
300
+
301
+
231
302
  if __name__ == "__main__":
232
- import time
303
+ # 参数列表:每个参数是元组
304
+ params_list = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
305
+
306
+ # 创建并行执行器
307
+ executor = ParallelExecutor()
308
+
309
+ # 并行运行
310
+ results = executor.run(_test_sum, params_list)
311
+
312
+ # 验证结果顺序
313
+ print("Params:", params_list)
314
+ print("Results:", results)
315
+ pass
316
+ """ import time
233
317
 
234
318
  import matplotlib.pyplot as plt
235
319
 
@@ -263,4 +347,4 @@ if __name__ == "__main__":
263
347
  plt.figure()
264
348
  plt.contourf(target_x, target_y, interpolated_data[0, 0, :, :])
265
349
  plt.colorbar()
266
- plt.show()
350
+ plt.show() """
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """
4
+ Author: Liu Kun && 16031215@qq.com
5
+ Date: 2024-09-17 17:26:11
6
+ LastEditors: Liu Kun && 16031215@qq.com
7
+ LastEditTime: 2024-11-21 13:10:47
8
+ FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_draw.py
9
+ Description:
10
+ EditPlatform: vscode
11
+ ComputerInfo: XPS 15 9510
12
+ SystemInfo: Windows 11
13
+ Python Version: 3.11
14
+ """
15
+
16
+
17
+ import warnings
18
+
19
+ import cartopy.crs as ccrs
20
+ import cartopy.feature as cfeature
21
+ import matplotlib as mpl
22
+ import matplotlib.pyplot as plt
23
+ import numpy as np
24
+ import xarray as xr
25
+ from cartopy.mpl.ticker import LatitudeFormatter, LongitudeFormatter
26
+
27
+ __all__ = ["fig_minus", "gif", "add_cartopy", "add_gridlines", "MidpointNormalize", "add_lonlat_unit", "contour", "contourf", "quiver"]
28
+
29
+ warnings.filterwarnings("ignore")
30
+
31
+
32
+ def fig_minus(ax_x=None, ax_y=None, cbar=None, decimal=None, add_space=False):
33
+ """
34
+ Description: 将坐标轴刻度中的负号替换为减号
35
+
36
+ param {*} ax_x : x轴
37
+ param {*} ax_y : y轴
38
+ param {*} cbar : colorbar
39
+ param {*} decimal : 小数位数
40
+ param {*} add_space : 是否在非负数前面加空格
41
+
42
+ return {*} ax_x or ax_y or cbar
43
+ """
44
+ if ax_x is not None:
45
+ current_ticks = ax_x.get_xticks()
46
+ if ax_y is not None:
47
+ current_ticks = ax_y.get_yticks()
48
+ if cbar is not None:
49
+ current_ticks = cbar.get_ticks()
50
+ # 先判断是否需要加空格,如果要,先获取需要加的索引
51
+ if add_space:
52
+ index = 0
53
+ for _, tick in enumerate(current_ticks):
54
+ if tick >= 0:
55
+ index = _
56
+ break
57
+ if decimal is not None:
58
+ # my_ticks = [(round(float(iii), decimal)) for iii in my_ticks]
59
+ current_ticks = [f"{val:.{decimal}f}" if val != 0 else "0" for val in current_ticks]
60
+
61
+ out_ticks = [f"{val}".replace("-", "\u2212") for val in current_ticks]
62
+ if add_space:
63
+ # 在非负数前面加两个空格
64
+ out_ticks[index:] = [" " + m for m in out_ticks[index:]]
65
+
66
+ if ax_x is not None:
67
+ ax_x.set_xticklabels(out_ticks)
68
+ return ax_x
69
+ if ax_y is not None:
70
+ ax_y.set_yticklabels(out_ticks)
71
+ return ax_y
72
+ if cbar is not None:
73
+ cbar.set_ticklabels(out_ticks)
74
+ return cbar
75
+
76
+
77
+ # ** 将生成图片/已有图片制作成动图
78
+ def gif(image_list: list, gif_name: str, duration=0.2): # 制作动图,默认间隔0.2
79
+ """
80
+ Description
81
+ Make gif from images
82
+ Parameters
83
+ image_list : list, list of images
84
+ gif_name : str, name of gif
85
+ duration : float, duration of each frame
86
+ Returns
87
+ None
88
+ Example
89
+ gif(["1.png", "2.png"], "test.gif", duration=0.2)
90
+ """
91
+ import imageio.v2 as imageio
92
+
93
+ frames = []
94
+ for image_name in image_list:
95
+ frames.append(imageio.imread(image_name))
96
+ imageio.mimsave(gif_name, frames, format="GIF", duration=duration)
97
+ print("Gif制作完成!")
98
+ return
99
+
100
+
101
+ # ** 转化经/纬度刻度
102
+ def add_lonlat_unit(lon=None, lat=None, decimal=2):
103
+ """
104
+ param {*} lon : 经度列表
105
+ param {*} lat : 纬度列表
106
+ param {*} decimal : 小数位数
107
+ return {*} 转化后的经/纬度列表
108
+ example : add_lonlat_unit(lon=lon, lat=lat, decimal=2)
109
+ """
110
+
111
+ def _format_longitude(x_list):
112
+ out_list = []
113
+ for x in x_list:
114
+ if x > 180:
115
+ x -= 360
116
+ # degrees = int(abs(x))
117
+ degrees = round(abs(x), decimal)
118
+ direction = "E" if x >= 0 else "W"
119
+ out_list.append(f"{degrees:.{decimal}f}°{direction}" if x != 0 and x != 180 else f"{degrees}°")
120
+ return out_list if len(out_list) > 1 else out_list[0]
121
+
122
+ def _format_latitude(y_list):
123
+ out_list = []
124
+ for y in y_list:
125
+ if y > 90:
126
+ y -= 180
127
+ # degrees = int(abs(y))
128
+ degrees = round(abs(y), decimal)
129
+ direction = "N" if y >= 0 else "S"
130
+ out_list.append(f"{degrees:.{decimal}f}°{direction}" if y != 0 else f"{degrees}°")
131
+ return out_list if len(out_list) > 1 else out_list[0]
132
+
133
+ if lon and lat:
134
+ return _format_longitude(lon), _format_latitude(lat)
135
+ elif lon:
136
+ return _format_longitude(lon)
137
+ elif lat:
138
+ return _format_latitude(lat)
139
+
140
+
141
+ # ** 添加网格线
142
+ def add_gridlines(ax, projection=ccrs.PlateCarree(), color="k", alpha=0.5, linestyle="--", linewidth=0.5):
143
+ # add gridlines
144
+ gl = ax.gridlines(crs=projection, draw_labels=True, linewidth=linewidth, color=color, alpha=alpha, linestyle=linestyle)
145
+ gl.right_labels = False
146
+ gl.top_labels = False
147
+ gl.xformatter = LongitudeFormatter(zero_direction_label=False)
148
+ gl.yformatter = LatitudeFormatter()
149
+
150
+ return ax, gl
151
+
152
+
153
+ # ** 添加地图
154
+ def add_cartopy(ax, lon=None, lat=None, projection=ccrs.PlateCarree(), gridlines=True, landcolor="lightgrey", oceancolor="lightblue", cartopy_linewidth=0.5):
155
+ # add coastlines
156
+ ax.add_feature(cfeature.LAND, facecolor=landcolor)
157
+ ax.add_feature(cfeature.OCEAN, facecolor=oceancolor)
158
+ ax.add_feature(cfeature.COASTLINE, linewidth=cartopy_linewidth)
159
+ # ax.add_feature(cfeature.BORDERS, linewidth=cartopy_linewidth, linestyle=":")
160
+
161
+ # add gridlines
162
+ if gridlines:
163
+ ax, gl = add_gridlines(ax, projection)
164
+
165
+ # set longitude and latitude format
166
+ lon_formatter = LongitudeFormatter(zero_direction_label=False)
167
+ lat_formatter = LatitudeFormatter()
168
+ ax.xaxis.set_major_formatter(lon_formatter)
169
+ ax.yaxis.set_major_formatter(lat_formatter)
170
+
171
+ # set extent
172
+ if lon is not None and lat is not None:
173
+ lon_min, lon_max = lon.min(), lon.max()
174
+ lat_min, lat_max = lat.min(), lat.max()
175
+ ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=projection)
176
+
177
+
178
+ # ** 自定义归一化类,使得0值处为中心点
179
+ class MidpointNormalize(mpl.colors.Normalize):
180
+ """
181
+ Description: 自定义归一化类,使得0值处为中心点
182
+
183
+ param {*} mpl.colors.Normalize : 继承Normalize类
184
+ return {*}
185
+
186
+ Example:
187
+ nrom = MidpointNormalize(vmin=-2, vmax=1, vcenter=0)
188
+ """
189
+
190
+ def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False):
191
+ self.vcenter = vcenter
192
+ super().__init__(vmin, vmax, clip)
193
+
194
+ def __call__(self, value, clip=None):
195
+ x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1.0]
196
+ return np.ma.masked_array(np.interp(value, x, y, left=-np.inf, right=np.inf))
197
+
198
+ def inverse(self, value):
199
+ y, x = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
200
+ return np.interp(value, x, y, left=-np.inf, right=np.inf)
201
+
202
+
203
+ # -----------------------------------------------------------------------------------------------------------------------------------------------------------------
204
+
205
+ # ** 绘制填色图
206
+ def contourf(data,x=None,y=None,cmap='coolwarm',show=True,store=None,cartopy=False):
207
+ """
208
+ Description: 绘制填色图
209
+
210
+ param {*} data : 二维数据
211
+ param {*} x : x轴坐标
212
+ param {*} y : y轴坐标
213
+ param {*} cmap : 颜色映射
214
+ param {*} show : 是否显示
215
+ param {*} store : 是否保存
216
+ param {*} cartopy : 是否使用cartopy
217
+
218
+ return {*}
219
+ """
220
+ data = np.array(data)
221
+ if x is None or y is None:
222
+ x = np.arange(data.shape[1])
223
+ y = np.arange(data.shape[0])
224
+ if cartopy:
225
+ fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree()})
226
+ add_cartopy(ax, lon=x, lat=y)
227
+ ax.contourf(x, y, data, transform=ccrs.PlateCarree(), cmap=cmap)
228
+ else:
229
+ plt.contourf(x, y, data, cmap=cmap)
230
+ plt.colorbar()
231
+ plt.savefig(store, dpi=600, bbox_inches="tight") if store else plt.show()
232
+ plt.close()
233
+
234
+
235
+ # ** 绘制等值线图
236
+ def contour(data, x=None, y=None, cmap="coolwarm", show=True, store=None, cartopy=False):
237
+ """
238
+ Description: 绘制等值线图
239
+
240
+ param {*} data : 二维数据
241
+ param {*} x : x轴坐标
242
+ param {*} y : y轴坐标
243
+ param {*} cmap : 颜色映射
244
+ param {*} show : 是否显示
245
+ param {*} store : 是否保存
246
+ param {*} cartopy : 是否使用cartopy
247
+
248
+ return {*}
249
+ """
250
+ data = np.array(data)
251
+ if x is None or y is None:
252
+ x = np.arange(data.shape[1])
253
+ y = np.arange(data.shape[0])
254
+ if cartopy:
255
+ fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()})
256
+ add_cartopy(ax, lon=x, lat=y)
257
+ cr = ax.contour(x, y, data, transform=ccrs.PlateCarree(), cmap=cmap)
258
+ else:
259
+ cr = plt.contour(x, y, data, cmap=cmap)
260
+ plt.clabel(cr, inline=True, fontsize=10)
261
+ plt.savefig(store, dpi=600, bbox_inches="tight") if store else plt.show()
262
+ plt.close()
263
+
264
+
265
+ # ** 绘制矢量场
266
+ def quiver(u, v, lon, lat, picname=None, cmap="coolwarm", scale=0.25, width=0.002, x_space=5, y_space=5):
267
+ """
268
+ param {*} u : 二维数据
269
+ param {*} v : 二维数据
270
+ param {*} lon : 经度, 1D or 2D
271
+ param {*} lat : 纬度, 1D or 2D
272
+ param {*} picname : 图片保存的文件名(含路径)
273
+ param {*} cmap : 颜色映射,默认coolwarm
274
+ param {*} scale : 箭头的大小 / 缩小程度
275
+ param {*} width : 箭头的宽度
276
+ param {*} x_space : x轴间隔
277
+ param {*} y_space : y轴间隔
278
+ return {*} 无返回值
279
+ """
280
+ # 创建新的网格位置变量(lat_c, lon_c)
281
+ if len(lon.shape) == 1 and len(lat.shape) == 1:
282
+ lon_c, lat_c = np.meshgrid(lon, lat)
283
+ else:
284
+ lon_c, lat_c = lon, lat
285
+
286
+ # 设置箭头的比例、颜色、宽度等参数
287
+ # scale = 0.25 # 箭头的大小 / 缩小程度
288
+ # color = '#E5D1FA'
289
+ # width = 0.002 # 箭头的宽度
290
+ # x_space = 1
291
+ # y_space = 1
292
+
293
+ # 计算矢量的大小
294
+ S = xr.DataArray(np.hypot(np.array(u), np.array(v)))
295
+
296
+ mean_S = S.nanmean()
297
+
298
+ # 使用 plt.quiver 函数绘制矢量图
299
+ # 通过设置 quiver 函数的 pivot 参数来指定箭头的位置
300
+ quiver_plot = plt.quiver(
301
+ lon_c[::y_space, ::x_space],
302
+ lat_c[::y_space, ::x_space],
303
+ u[::y_space, ::x_space],
304
+ v[::y_space, ::x_space],
305
+ S[::y_space, ::x_space], # 矢量的大小,可以不要
306
+ pivot="middle",
307
+ scale=scale,
308
+ # color=color, # 矢量的颜色,单色
309
+ cmap=cmap, # 矢量的颜色,多色
310
+ width=width,
311
+ )
312
+ # plt.quiverkey(quiver_plot, X=0.90, Y=0.975, U=1, label='1 m/s', labelpos='E', fontproperties={'size': 10})
313
+ plt.quiverkey(quiver_plot, X=0.87, Y=0.975, U=mean_S, label=f"{mean_S:.2f} m/s", labelpos="E", fontproperties={"size": 10})
314
+ plt.colorbar(quiver_plot)
315
+ plt.xlabel("X")
316
+ plt.ylabel("Y")
317
+
318
+ plt.savefig(picname, bbox_inches="tight") if picname is not None else plt.show()
319
+ plt.clf()
320
+ plt.close()
321
+
322
+
323
+
324
+ if __name__ == "__main__":
325
+ pass