oafuncs 0.0.98.31__py3-none-any.whl → 0.0.98.32__py3-none-any.whl

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.
@@ -300,11 +300,13 @@ class ColorProgressBar:
300
300
  # 获取终端宽度
301
301
  try:
302
302
  term_width = self.bar_length or (shutil.get_terminal_size().columns if self._is_terminal else 80)
303
+ # print(f'Terminal width: {term_width}') # 调试输出
303
304
  except (AttributeError, OSError):
304
305
  term_width = 80 # 默认终端宽度
305
306
 
306
307
  # 确保有效宽度不小于最低限制
307
- effective_width = max(15, term_width - 40)
308
+ # effective_width = max(15, term_width - 40)
309
+ effective_width = max(15, int(term_width * 0.6)) # 保留40个字符用于其他信息
308
310
  if effective_width < 10:
309
311
  warnings.warn("Terminal width is too small for proper progress bar rendering.")
310
312
  effective_width = 10 # 设置最低宽度限制
oafuncs/_script/email.py CHANGED
@@ -1,21 +1,3 @@
1
- #!/usr/bin/env python
2
- # coding=utf-8
3
- """
4
- Author: Liu Kun && 16031215@qq.com
5
- Date: 2025-04-04 20:21:59
6
- LastEditors: Liu Kun && 16031215@qq.com
7
- LastEditTime: 2025-04-04 20:21:59
8
- FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\_script\\email.py
9
- Description:
10
- EditPlatform: vscode
11
- ComputerInfo: XPS 15 9510
12
- SystemInfo: Windows 11
13
- Python Version: 3.12
14
- """
15
-
16
-
17
-
18
-
19
1
  import random
20
2
  import smtplib
21
3
  from email.header import Header
@@ -26,7 +8,6 @@ from rich import print
26
8
 
27
9
  __all__ = ["send"]
28
10
 
29
-
30
11
  def _email_info():
31
12
  email_dict = {
32
13
  "liukun0312@vip.qq.com": [4, 13, -10, 2, -10, 4, -7, -8, 8, -1, 3, -2, -11, -6, -9, -7],
oafuncs/oa_cmap.py CHANGED
@@ -258,10 +258,17 @@ def get(colormap_name: Optional[str] = None, show_available: bool = False) -> Op
258
258
  "cool_1": ["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC"],
259
259
  "warm_1": ["#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"],
260
260
  # ---------------------------------------------------------------------------
261
+ # suitable for diverging color maps, wind vector
261
262
  "diverging_2": ["#4A235A", "#1F618D", "#1ABC9C", "#A9DFBF", "#F2F3F4", "#FDEBD0", "#F5B041", "#E74C3C", "#78281F"],
262
263
  "cool_2": ["#4A235A", "#1F618D", "#1ABC9C", "#A9DFBF", "#F2F3F4"],
263
264
  "warm_2": ["#F2F3F4", "#FDEBD0", "#F5B041", "#E74C3C", "#78281F"],
264
265
  # ---------------------------------------------------------------------------
266
+ "diverging_3": ["#1a66f2", "#5DADE2", "#48C9B0", "#A9DFBF", "#F2F3F4", "#FFDAB9", "#FF9E80", "#FF6F61", "#FF1744"],
267
+ "cool_3": ["#1a66f2", "#5DADE2", "#48C9B0", "#A9DFBF", "#F2F3F4"],
268
+ "warm_3": ["#F2F3F4", "#FFDAB9", "#FF9E80", "#FF6F61", "#FF1744"],
269
+ # ---------------------------------------------------------------------------
270
+ "diverging_4": ["#5DADE2", "#A2D9F7", "#D6EAF8", "#F2F3F4", "#FADBD8", "#F1948A", "#E74C3C"],
271
+ # ----------------------------------------------------------------------------
265
272
  "colorful_1": ["#6d00db", "#9800cb", "#F2003C", "#ff4500", "#ff7f00", "#FE28A2", "#FFC0CB", "#DDA0DD", "#40E0D0", "#1a66f2", "#00f7fb", "#8fff88", "#E3FF00"],
266
273
  }
267
274
 
oafuncs/oa_date.py CHANGED
@@ -1,20 +1,6 @@
1
- #!/usr/bin/env python
2
- # coding=utf-8
3
- """
4
- Author: Liu Kun && 16031215@qq.com
5
- Date: 2025-03-27 16:56:57
6
- LastEditors: Liu Kun && 16031215@qq.com
7
- LastEditTime: 2025-04-04 12:58:15
8
- FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_date.py
9
- Description:
10
- EditPlatform: vscode
11
- ComputerInfo: XPS 15 9510
12
- SystemInfo: Windows 11
13
- Python Version: 3.12
14
- """
15
-
16
1
  import calendar
17
2
  import datetime
3
+ import functools
18
4
  from typing import List, Optional
19
5
 
20
6
  from rich import print
@@ -64,15 +50,16 @@ def hour_range(start_time: str, end_time: str, hour_interval: int = 6) -> List[s
64
50
  date_s += datetime.timedelta(hours=hour_interval)
65
51
  return date_list
66
52
 
53
+
67
54
  def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", output_format: Optional[str] = None) -> str:
68
55
  """
69
56
  Adjust a given base time by adding a specified time delta.
70
57
 
71
58
  Args:
72
- base_time (str): Base time in the format "yyyymmdd" to "yyyymmddHHMMSS".
73
- Missing parts are assumed to be "0".
59
+ base_time (str): Base time in the format "yyyy" to "yyyymmddHHMMSS".
60
+ Missing parts are padded with appropriate defaults.
74
61
  time_delta (int): The amount of time to add.
75
- delta_unit (str): The unit of time to add ("seconds", "minutes", "hours", "days").
62
+ delta_unit (str): The unit of time to add ("seconds", "minutes", "hours", "days", "months", "years").
76
63
  output_format (str, optional): Custom output format for the adjusted time. Defaults to None.
77
64
 
78
65
  Returns:
@@ -84,17 +71,31 @@ def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", outp
84
71
  >>> adjust_time("20240101000000", 2, "hours", "%Y-%m-%d %H:%M:%S")
85
72
  '2024-01-01 02:00:00'
86
73
  >>> adjust_time("20240101000000", 30, "minutes")
87
- '2024-01-01 00:30:00'
74
+ '20240101003000'
88
75
  """
89
76
  # Normalize the input time to "yyyymmddHHMMSS" format
90
77
  time_format = "%Y%m%d%H%M%S"
91
- if len(base_time) == 4:
92
- base_time += "0101"
93
- elif len(base_time) == 6:
94
- base_time += "01"
95
- base_time = base_time.ljust(14, "0")
96
78
 
97
- time_obj = datetime.datetime.strptime(base_time, time_format)
79
+ # Pad the time string to full format
80
+ if len(base_time) == 4: # yyyy
81
+ base_time += "0101000000"
82
+ elif len(base_time) == 6: # yyyymm
83
+ base_time += "01000000"
84
+ elif len(base_time) == 8: # yyyymmdd
85
+ base_time += "000000"
86
+ elif len(base_time) == 10: # yyyymmddhh
87
+ base_time += "0000"
88
+ elif len(base_time) == 12: # yyyymmddhhmm
89
+ base_time += "00"
90
+ elif len(base_time) == 14: # yyyymmddhhmmss
91
+ pass # Already complete
92
+ else:
93
+ raise ValueError(f"Invalid base_time format. Expected 4-14 digits, got {len(base_time)}")
94
+
95
+ try:
96
+ time_obj = datetime.datetime.strptime(base_time, time_format)
97
+ except ValueError as e:
98
+ raise ValueError(f"Invalid date format: {base_time}. Error: {e}")
98
99
 
99
100
  # Add the specified amount of time
100
101
  if delta_unit == "seconds":
@@ -114,8 +115,18 @@ def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", outp
114
115
  time_obj = time_obj.replace(year=year, month=month, day=day)
115
116
  elif delta_unit == "years":
116
117
  # Handle year addition separately
117
- year = time_obj.year + time_delta
118
- time_obj = time_obj.replace(year=year)
118
+ try:
119
+ year = time_obj.year + time_delta
120
+ # Handle leap year edge case for Feb 29
121
+ if time_obj.month == 2 and time_obj.day == 29:
122
+ if not calendar.isleap(year):
123
+ time_obj = time_obj.replace(year=year, day=28)
124
+ else:
125
+ time_obj = time_obj.replace(year=year)
126
+ else:
127
+ time_obj = time_obj.replace(year=year)
128
+ except ValueError as e:
129
+ raise ValueError(f"Invalid year calculation: {e}")
119
130
  else:
120
131
  raise ValueError("Invalid time unit. Use 'seconds', 'minutes', 'hours', 'days', 'months', or 'years'.")
121
132
 
@@ -123,19 +134,9 @@ def adjust_time(base_time: str, time_delta: int, delta_unit: str = "hours", outp
123
134
  if output_format:
124
135
  return time_obj.strftime(output_format)
125
136
  else:
126
- if delta_unit == "seconds":
127
- default_format = "%Y%m%d%H%M%S"
128
- elif delta_unit == "minutes":
129
- default_format = "%Y%m%d%H%M"
130
- elif delta_unit == "hours":
131
- default_format = "%Y%m%d%H"
132
- elif delta_unit == "days":
133
- default_format = "%Y%m%d"
134
- elif delta_unit == "months":
135
- default_format = "%Y%m"
136
- elif delta_unit == "years":
137
- default_format = "%Y"
138
- return time_obj.strftime(default_format)
137
+ # Use default format based on delta_unit
138
+ format_map = {"seconds": "%Y%m%d%H%M%S", "minutes": "%Y%m%d%H%M%S", "hours": "%Y%m%d%H", "days": "%Y%m%d", "months": "%Y%m", "years": "%Y"}
139
+ return time_obj.strftime(format_map[delta_unit])
139
140
 
140
141
 
141
142
  class timeit:
@@ -154,26 +155,29 @@ class timeit:
154
155
  Example:
155
156
  @timeit(log_to_file=True, display_time=True)
156
157
  def example_function():
157
- # Simulate some work
158
+ import time
158
159
  time.sleep(2)
159
160
  """
160
161
 
161
- def __init__(self, func, log_to_file: bool = False, display_time: bool = True):
162
- self.func = func
162
+ def __init__(self, log_to_file: bool = False, display_time: bool = True):
163
163
  self.log_to_file = log_to_file
164
164
  self.display_time = display_time
165
165
 
166
- def __call__(self, *args, **kwargs):
167
- start_time = datetime.datetime.now()
168
- result = self.func(*args, **kwargs)
169
- end_time = datetime.datetime.now()
170
- elapsed_time = (end_time - start_time).total_seconds()
166
+ def __call__(self, func):
167
+ @functools.wraps(func)
168
+ def wrapper(*args, **kwargs):
169
+ start_time = datetime.datetime.now()
170
+ result = func(*args, **kwargs)
171
+ end_time = datetime.datetime.now()
172
+ elapsed_time = (end_time - start_time).total_seconds()
173
+
174
+ if self.display_time:
175
+ print(f"[bold green]Function '{func.__name__}' executed in {elapsed_time:.2f} seconds.[/bold green]")
171
176
 
172
- if self.display_time:
173
- print(f"[bold green]Function '{self.func.__name__}' executed in {elapsed_time:.2f} seconds.[/bold green]")
177
+ if self.log_to_file:
178
+ with open("execution_time.log", "a", encoding="utf-8") as log_file:
179
+ log_file.write(f"{datetime.datetime.now()} - Function '{func.__name__}' executed in {elapsed_time:.2f} seconds.\n")
174
180
 
175
- if self.log_to_file:
176
- with open("execution_time.log", "a") as log_file:
177
- log_file.write(f"{datetime.datetime.now()} - Function '{self.func.__name__}' executed in {elapsed_time:.2f} seconds.\n")
181
+ return result
178
182
 
179
- return result
183
+ return wrapper
@@ -719,7 +719,7 @@ class _HycomDownloader:
719
719
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
720
720
  # print(f"{timestamp} - INFO - ", end="") # Output log prefix without newline
721
721
  # print("[bold #3dfc40]Success")
722
- print(f"{timestamp} - RESULT - [bold #3dfc40]Success")
722
+ print(f"{timestamp} - INFO - [bold #3dfc40]Success")
723
723
  return
724
724
 
725
725
  except Exception as e:
@@ -736,7 +736,7 @@ class _HycomDownloader:
736
736
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
737
737
  # print(f"{timestamp} - ERROR - ", end="")
738
738
  # print("[bold red]Failed")
739
- print(f"{timestamp} - RESULT - [bold red]Failure")
739
+ print(f"{timestamp} - INFO - [bold red]Failure")
740
740
  return
741
741
 
742
742
  async def run(self):
oafuncs/oa_draw.py CHANGED
@@ -15,16 +15,16 @@ Python Version: 3.11
15
15
 
16
16
  import warnings
17
17
 
18
- import cv2
19
18
  import cartopy.crs as ccrs
20
19
  import cartopy.feature as cfeature
20
+ import cv2
21
21
  import matplotlib as mpl
22
22
  import matplotlib.pyplot as plt
23
23
  import numpy as np
24
24
  from cartopy.mpl.ticker import LatitudeFormatter, LongitudeFormatter
25
25
  from rich import print
26
26
 
27
- __all__ = ["fig_minus", "gif", "add_cartopy", "add_gridlines", "MidpointNormalize", "add_lonlat_unit"]
27
+ __all__ = ["fig_minus", "gif", "movie", "setup_map", "MidpointNormalize"]
28
28
 
29
29
  warnings.filterwarnings("ignore")
30
30
 
@@ -43,15 +43,24 @@ def fig_minus(x_axis: plt.Axes = None, y_axis: plt.Axes = None, colorbar: mpl.co
43
43
  plt.Axes | mpl.colorbar.Colorbar | None: The modified axis or colorbar object.
44
44
 
45
45
  Example:
46
- >>> fig_minus(x_axis=ax, y_axis=None, colorbar=colorbar, decimal_places=2, add_spacing=True)
46
+ >>> fig_minus(x_axis=ax, decimal_places=2, add_spacing=True)
47
47
  """
48
+ current_ticks = None
49
+ target_object = None
50
+
48
51
  # Determine which object to use and get its ticks
49
52
  if x_axis is not None:
50
53
  current_ticks = x_axis.get_xticks()
51
- if y_axis is not None:
54
+ target_object = x_axis
55
+ elif y_axis is not None:
52
56
  current_ticks = y_axis.get_yticks()
53
- if colorbar is not None:
57
+ target_object = y_axis
58
+ elif colorbar is not None:
54
59
  current_ticks = colorbar.get_ticks()
60
+ target_object = colorbar
61
+ else:
62
+ print("[yellow]Warning:[/yellow] No valid axis or colorbar provided.")
63
+ return None
55
64
 
56
65
  # Find index for adding space to non-negative values if needed
57
66
  if add_spacing:
@@ -75,326 +84,314 @@ def fig_minus(x_axis: plt.Axes = None, y_axis: plt.Axes = None, colorbar: mpl.co
75
84
  # Apply formatted ticks to the appropriate object
76
85
  if x_axis is not None:
77
86
  x_axis.set_xticklabels(out_ticks)
78
- if y_axis is not None:
87
+ elif y_axis is not None:
79
88
  y_axis.set_yticklabels(out_ticks)
80
- if colorbar is not None:
89
+ elif colorbar is not None:
81
90
  colorbar.set_ticklabels(out_ticks)
82
91
 
83
92
  print("[green]Axis tick labels updated successfully.[/green]")
84
- return x_axis or y_axis or colorbar
93
+ return target_object
85
94
 
86
95
 
87
- def gif(image_paths: list[str], output_gif_name: str, frame_duration: float = 200, resize_dimensions: tuple[int, int] = None) -> None:
96
+ def gif(image_paths: list[str], output_gif_name: str, frame_duration: float = 0.2, resize_dimensions: tuple[int, int] = None) -> None:
88
97
  """Create a GIF from a list of images.
89
98
 
90
99
  Args:
91
100
  image_paths (list[str]): List of image file paths.
92
101
  output_gif_name (str): Name of the output GIF file.
93
- frame_duration (float): Duration of each frame in milliseconds.
102
+ frame_duration (float): Duration of each frame in seconds. Defaults to 0.2.
94
103
  resize_dimensions (tuple[int, int], optional): Resize dimensions (width, height). Defaults to None.
95
104
 
96
105
  Returns:
97
106
  None
98
107
 
99
108
  Example:
100
- >>> gif(['image1.png', 'image2.png'], 'output.gif', frame_duration=200, resize_dimensions=(800, 600))
109
+ >>> gif(['image1.png', 'image2.png'], 'output.gif', frame_duration=0.5, resize_dimensions=(800, 600))
101
110
  """
102
111
  import imageio.v2 as imageio
103
- import numpy as np
104
112
  from PIL import Image
105
113
 
114
+ if not image_paths:
115
+ print("[red]Error:[/red] Image paths list is empty.")
116
+ return
117
+
106
118
  frames = []
107
119
 
108
- # 获取目标尺寸
120
+ # Get target dimensions
109
121
  if resize_dimensions is None and image_paths:
110
- # 使用第一张图片的尺寸作为标准
111
122
  with Image.open(image_paths[0]) as img:
112
123
  resize_dimensions = img.size
113
124
 
114
- # 读取并调整所有图片的尺寸
125
+ # Read and resize all images
115
126
  for image_name in image_paths:
116
- with Image.open(image_name) as img:
117
- if resize_dimensions:
118
- img = img.resize(resize_dimensions, Image.LANCZOS)
119
- frames.append(np.array(img))
127
+ try:
128
+ with Image.open(image_name) as img:
129
+ if resize_dimensions:
130
+ img = img.resize(resize_dimensions, Image.LANCZOS)
131
+ frames.append(np.array(img))
132
+ except Exception as e:
133
+ print(f"[yellow]Warning:[/yellow] Failed to read image {image_name}: {e}")
134
+ continue
120
135
 
121
- # 修改此处:明确使用 frame_duration 值,并将其作为每帧的持续时间(以秒为单位)
122
- # 某些版本的 imageio 可能需要以毫秒为单位,或者使用 fps 参数
136
+ if not frames:
137
+ print("[red]Error:[/red] No valid images found.")
138
+ return
139
+
140
+ # Create GIF
123
141
  try:
124
- # 先尝试直接使用 frame_duration 参数(以秒为单位)
125
142
  imageio.mimsave(output_gif_name, frames, format="GIF", duration=frame_duration)
143
+ print(f"[green]GIF created successfully![/green] Size: {resize_dimensions}, Frame duration: {frame_duration}s")
126
144
  except Exception as e:
127
- print(f"[yellow]Warning:[/yellow] Attempting to use fps parameter instead of duration: {e}")
128
- # 如果失败,尝试使用 fps 参数(fps = 1/frame_duration)
129
- fps = 1.0 / frame_duration if frame_duration > 0 else 5.0
130
- imageio.mimsave(output_gif_name, frames, format="GIF", fps=fps)
145
+ print(f"[red]Error:[/red] Failed to create GIF: {e}")
131
146
 
132
- print(f"[green]GIF created successfully![/green] Size: {resize_dimensions}, Frame interval: {frame_duration} ms")
133
- return
134
147
 
135
-
136
- def movie(image_files, output_video_path, fps):
137
- """
138
- 从图像文件列表创建视频。
148
+ def movie(image_files: list[str], output_video_path: str, fps: int) -> None:
149
+ """Create a video from a list of image files.
139
150
 
140
151
  Args:
141
- image_files (list): 按顺序排列的图像文件路径列表。
142
- output_video_path (str): 输出视频文件的路径 (例如 'output.mp4')
143
- fps (int): 视频的帧率。
152
+ image_files (list[str]): List of image file paths in order.
153
+ output_video_path (str): Output video file path (e.g., 'output.mp4').
154
+ fps (int): Video frame rate.
155
+
156
+ Returns:
157
+ None
158
+
159
+ Example:
160
+ >>> movie(['img1.jpg', 'img2.jpg'], 'output.mp4', fps=30)
144
161
  """
145
162
  if not image_files:
146
- print("错误:图像文件列表为空。")
163
+ print("[red]Error:[/red] Image files list is empty.")
147
164
  return
148
165
 
149
- # 读取第一张图片以获取帧尺寸
166
+ # Read first image to get frame dimensions
150
167
  try:
151
168
  frame = cv2.imread(image_files[0])
152
169
  if frame is None:
153
- print(f"错误:无法读取第一张图片:{image_files[0]}")
170
+ print(f"[red]Error:[/red] Cannot read first image: {image_files[0]}")
154
171
  return
155
172
  height, width, layers = frame.shape
156
173
  size = (width, height)
157
- print(f"视频尺寸设置为:{size}")
174
+ print(f"Video dimensions set to: {size}")
158
175
  except Exception as e:
159
- print(f"读取第一张图片时出错:{e}")
176
+ print(f"[red]Error:[/red] Error reading first image: {e}")
160
177
  return
161
178
 
162
- # 选择编解码器并创建VideoWriter对象
163
- # 对于 .mp4 文件,常用 'mp4v' 或 'avc1'
164
- # 对于 .avi 文件,常用 'XVID' 或 'MJPG'
165
- fourcc = cv2.VideoWriter_fourcc(*"mp4v") # 或者尝试 'avc1', 'XVID' 等
179
+ # Create VideoWriter object
180
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
166
181
  out = cv2.VideoWriter(output_video_path, fourcc, fps, size)
167
182
 
168
183
  if not out.isOpened():
169
- print(f"错误:无法打开视频文件进行写入:{output_video_path}")
170
- print("请检查编解码器 ('fourcc') 是否受支持以及路径是否有效。")
184
+ print(f"[red]Error:[/red] Cannot open video file for writing: {output_video_path}")
185
+ print("Please check if the codec is supported and the path is valid.")
171
186
  return
172
187
 
173
- print(f"开始将图像写入视频:{output_video_path}...")
188
+ print(f"Starting to write images to video: {output_video_path}...")
189
+ successful_frames = 0
190
+
174
191
  for i, filename in enumerate(image_files):
175
192
  try:
176
193
  frame = cv2.imread(filename)
177
194
  if frame is None:
178
- print(f"警告:跳过无法读取的图像:{filename}")
195
+ print(f"[yellow]Warning:[/yellow] Skipping unreadable image: {filename}")
179
196
  continue
180
- # 确保帧尺寸与初始化时相同,如果需要可以调整大小
197
+
198
+ # Ensure frame dimensions match initialization
181
199
  current_height, current_width, _ = frame.shape
182
200
  if (current_width, current_height) != size:
183
- print(f"警告:图像 {filename} 的尺寸 ({current_width}, {current_height}) 与初始尺寸 {size} 不同。将调整大小。")
184
201
  frame = cv2.resize(frame, size)
185
202
 
186
203
  out.write(frame)
187
- # 打印进度(可选)
204
+ successful_frames += 1
205
+
206
+ # Print progress
188
207
  if (i + 1) % 50 == 0 or (i + 1) == len(image_files):
189
- print(f"已处理 {i + 1}/{len(image_files)} ")
208
+ print(f"Processed {i + 1}/{len(image_files)} frames")
190
209
 
191
210
  except Exception as e:
192
- print(f"处理图像 {filename} 时出错:{e}")
193
- continue # 跳过有问题的帧
211
+ print(f"[yellow]Warning:[/yellow] Error processing image {filename}: {e}")
212
+ continue
194
213
 
195
- # 释放资源
214
+ # Release resources
196
215
  out.release()
197
- print(f"视频创建成功:{output_video_path}")
198
-
199
-
200
- def add_lonlat_unit(longitudes: list[float] = None, latitudes: list[float] = None, decimal_places: int = 2) -> tuple[list[str], list[str]] | list[str]:
201
- """Convert longitude and latitude values to formatted string labels.
216
+ print(f"[green]Video created successfully:[/green] {output_video_path} ({successful_frames} frames)")
217
+
218
+
219
+ def setup_map(
220
+ axes: plt.Axes,
221
+ longitude_data: np.ndarray = None,
222
+ latitude_data: np.ndarray = None,
223
+ map_projection: ccrs.Projection = ccrs.PlateCarree(),
224
+ # Map features
225
+ show_land: bool = True,
226
+ show_ocean: bool = True,
227
+ show_coastline: bool = True,
228
+ show_borders: bool = False,
229
+ land_color: str = "lightgrey",
230
+ ocean_color: str = "lightblue",
231
+ coastline_linewidth: float = 0.5,
232
+ # Gridlines and ticks
233
+ show_gridlines: bool = False,
234
+ longitude_ticks: list[float] = None,
235
+ latitude_ticks: list[float] = None,
236
+ tick_decimals: int = 0,
237
+ # Gridline styling
238
+ grid_color: str = "k",
239
+ grid_alpha: float = 0.5,
240
+ grid_style: str = "--",
241
+ grid_width: float = 0.5,
242
+ # Label options
243
+ show_labels: bool = True,
244
+ left_labels: bool = True,
245
+ bottom_labels: bool = True,
246
+ right_labels: bool = False,
247
+ top_labels: bool = False,
248
+ ) -> plt.Axes:
249
+ """Setup a complete cartopy map with customizable features.
202
250
 
203
251
  Args:
204
- longitudes (list[float], optional): List of longitude values to format.
205
- latitudes (list[float], optional): List of latitude values to format.
206
- decimal_places (int, optional): Number of decimal places to display. Defaults to 2.
207
-
208
- Returns:
209
- tuple[list[str], list[str]] | list[str]: Formatted longitude and/or latitude labels.
210
- Returns a tuple of two lists if both longitudes and latitudes are provided,
211
- otherwise returns a single list of formatted values.
212
-
213
- Examples:
214
- >>> add_lonlat_unit(longitudes=[120, 180], latitudes=[30, 60], decimal_places=1)
215
- (['120.0°E', '180.0°'], ['30.0°N', '60.0°N'])
216
- >>> add_lonlat_unit(longitudes=[120, -60])
217
- ['120.00°E', '60.00°W']
218
- """
219
-
220
- def _format_longitude(longitude_values: list[float]) -> list[str] | str:
221
- """Format longitude values to string labels with directional indicators.
222
-
223
- Converts numerical longitude values to formatted strings with degree symbols
224
- and East/West indicators. Values outside the -180 to 180 range are normalized.
225
-
226
- Args:
227
- longitude_values: List of longitude values to format.
228
-
229
- Returns:
230
- List of formatted strings if input contains multiple values,
231
- or a single string if input contains just one value.
232
- """
233
- out_list = []
234
- for x in longitude_values:
235
- if x > 180 or x < -180:
236
- print(f"[yellow]Warning:[/yellow] Longitude value {x} outside normal range (-180 to 180)")
237
- x = ((x + 180) % 360) - 180 # Normalize to -180 to 180 range
238
-
239
- degrees = round(abs(x), decimal_places)
240
- direction = "E" if x >= 0 else "W"
241
- out_list.append(f"{degrees:.{decimal_places}f}°{direction}" if x != 0 and x != 180 else f"{degrees}°")
242
- return out_list if len(out_list) > 1 else out_list[0]
243
-
244
- def _format_latitude(latitude_values: list[float]) -> list[str] | str:
245
- """Format latitude values to string labels with directional indicators.
246
-
247
- Converts numerical latitude values to formatted strings with degree symbols
248
- and North/South indicators. Values outside the -90 to 90 range are normalized.
249
-
250
- Args:
251
- latitude_values (list[float]): List of latitude values to format
252
-
253
- Returns:
254
- list[str] | str: List of formatted strings if input contains multiple values,
255
- or a single string if input contains just one value
256
- """
257
- out_list = []
258
- for y in latitude_values:
259
- if y > 90 or y < -90:
260
- print(f"[yellow]Warning:[/yellow] Latitude value {y} outside valid range (-90 to 90)")
261
- y = min(max(y % 180 - 90, -90), 90) # Normalize to -90 to 90 range
262
-
263
- degrees = round(abs(y), decimal_places)
264
- direction = "N" if y >= 0 else "S"
265
- out_list.append(f"{degrees:.{decimal_places}f}°{direction}" if y != 0 else f"{degrees}°")
266
- return out_list if len(out_list) > 1 else out_list[0]
267
-
268
- # Input validation
269
- if longitudes is not None and not isinstance(longitudes, list):
270
- longitudes = [longitudes] # Convert single value to list
271
- if latitudes is not None and not isinstance(latitudes, list):
272
- latitudes = [latitudes] # Convert single value to list
273
-
274
- if longitudes and latitudes:
275
- result = _format_longitude(longitudes), _format_latitude(latitudes)
276
- elif longitudes:
277
- result = _format_longitude(longitudes)
278
- elif latitudes:
279
- result = _format_latitude(latitudes)
280
- else:
281
- result = []
282
-
283
- print("[green]Longitude and latitude values formatted successfully.[/green]")
284
- return result
285
-
286
-
287
- def add_gridlines(axes: plt.Axes, longitude_lines: list[float] = None, latitude_lines: list[float] = None, map_projection: ccrs.Projection = ccrs.PlateCarree(), line_color: str = "k", line_alpha: float = 0.5, line_style: str = "--", line_width: float = 0.5) -> tuple[plt.Axes, mpl.ticker.Locator]:
288
- """Add gridlines to a map.
289
-
290
- Args:
291
- axes (plt.Axes): The axes to add gridlines to.
292
- longitude_lines (list[float], optional): List of longitude positions for gridlines.
293
- latitude_lines (list[float], optional): List of latitude positions for gridlines.
294
- map_projection (ccrs.Projection, optional): Coordinate reference system. Defaults to PlateCarree.
295
- line_color (str, optional): Line color. Defaults to "k".
296
- line_alpha (float, optional): Line transparency. Defaults to 0.5.
297
- line_style (str, optional): Line style. Defaults to "--".
298
- line_width (float, optional): Line width. Defaults to 0.5.
299
-
300
- Returns:
301
- tuple[plt.Axes, mpl.ticker.Locator]: The axes and gridlines objects.
302
-
303
- Example:
304
- >>> add_gridlines(axes, longitude_lines=[0, 30], latitude_lines=[-90, 90], map_projection=ccrs.PlateCarree())
305
- >>> axes, gl = add_gridlines(axes, longitude_lines=[0, 30], latitude_lines=[-90, 90])
306
- """
307
- from matplotlib import ticker as mticker
308
-
309
- # add gridlines
310
- gl = axes.gridlines(crs=map_projection, draw_labels=True, linewidth=line_width, color=line_color, alpha=line_alpha, linestyle=line_style)
311
- gl.right_labels = False
312
- gl.top_labels = False
313
- gl.xformatter = LongitudeFormatter(zero_direction_label=False)
314
- gl.yformatter = LatitudeFormatter()
315
-
316
- if longitude_lines is not None:
317
- gl.xlocator = mticker.FixedLocator(np.array(longitude_lines))
318
- if latitude_lines is not None:
319
- gl.ylocator = mticker.FixedLocator(np.array(latitude_lines))
320
-
321
- # print("[green]Gridlines added successfully.[/green]")
322
- return axes, gl
323
-
324
-
325
- def add_cartopy(axes: plt.Axes, longitude_data: np.ndarray = None, latitude_data: np.ndarray = None, map_projection: ccrs.Projection = ccrs.PlateCarree(), show_gridlines: bool = True, land_color: str = "lightgrey", ocean_color: str = "lightblue", coastline_linewidth: float = 0.5) -> None:
326
- """Add cartopy features to a map.
327
-
328
- Args:
329
- axes (plt.Axes): The axes to add map features to.
252
+ axes (plt.Axes): The axes to setup as a map.
330
253
  longitude_data (np.ndarray, optional): Array of longitudes to set map extent.
331
254
  latitude_data (np.ndarray, optional): Array of latitudes to set map extent.
332
255
  map_projection (ccrs.Projection, optional): Coordinate reference system. Defaults to PlateCarree.
333
- show_gridlines (bool, optional): Whether to add gridlines. Defaults to True.
256
+
257
+ show_land (bool, optional): Whether to show land features. Defaults to True.
258
+ show_ocean (bool, optional): Whether to show ocean features. Defaults to True.
259
+ show_coastline (bool, optional): Whether to show coastlines. Defaults to True.
260
+ show_borders (bool, optional): Whether to show country borders. Defaults to False.
334
261
  land_color (str, optional): Color of land. Defaults to "lightgrey".
335
262
  ocean_color (str, optional): Color of oceans. Defaults to "lightblue".
336
263
  coastline_linewidth (float, optional): Line width for coastlines. Defaults to 0.5.
337
264
 
338
- Returns:
339
- None
265
+ show_gridlines (bool, optional): Whether to show gridlines. Defaults to False.
266
+ longitude_ticks (list[float], optional): Longitude tick positions.
267
+ latitude_ticks (list[float], optional): Latitude tick positions.
268
+ tick_decimals (int, optional): Number of decimal places for tick labels. Defaults to 0.
340
269
 
341
- Example:
342
- >>> add_cartopy(axes, longitude_data=lon_data, latitude_data=lat_data, map_projection=ccrs.PlateCarree(), show_gridlines=True)
343
- >>> axes = add_cartopy(axes, longitude_data=None, latitude_data=None, map_projection=ccrs.PlateCarree(), show_gridlines=False)
270
+ grid_color (str, optional): Gridline color. Defaults to "k".
271
+ grid_alpha (float, optional): Gridline transparency. Defaults to 0.5.
272
+ grid_style (str, optional): Gridline style. Defaults to "--".
273
+ grid_width (float, optional): Gridline width. Defaults to 0.5.
344
274
 
345
- """
346
- # add coastlines
347
- axes.add_feature(cfeature.LAND, facecolor=land_color)
348
- axes.add_feature(cfeature.OCEAN, facecolor=ocean_color)
349
- axes.add_feature(cfeature.COASTLINE, linewidth=coastline_linewidth)
350
- # axes.add_feature(cfeature.BORDERS, linewidth=coastline_linewidth, linestyle=":")
275
+ show_labels (bool, optional): Whether to show coordinate labels. Defaults to True.
276
+ left_labels (bool, optional): Show labels on left side. Defaults to True.
277
+ bottom_labels (bool, optional): Show labels on bottom. Defaults to True.
278
+ right_labels (bool, optional): Show labels on right side. Defaults to False.
279
+ top_labels (bool, optional): Show labels on top. Defaults to False.
351
280
 
352
- # add gridlines
353
- if show_gridlines:
354
- axes, gl = add_gridlines(axes, map_projection=map_projection)
281
+ Returns:
282
+ plt.Axes: The configured map axes.
355
283
 
356
- # set longitude and latitude format
357
- lon_formatter = LongitudeFormatter(zero_direction_label=False)
358
- lat_formatter = LatitudeFormatter()
359
- axes.xaxis.set_major_formatter(lon_formatter)
360
- axes.yaxis.set_major_formatter(lat_formatter)
284
+ Examples:
285
+ >>> # Basic map setup
286
+ >>> ax = setup_map(ax)
287
+
288
+ >>> # Map with gridlines and custom extent
289
+ >>> ax = setup_map(ax, longitude_data=lon, latitude_data=lat, show_gridlines=True)
290
+
291
+ >>> # Customized map
292
+ >>> ax = setup_map(
293
+ ... ax,
294
+ ... show_gridlines=True,
295
+ ... longitude_ticks=[0, 30, 60],
296
+ ... latitude_ticks=[-30, 0, 30],
297
+ ... land_color='wheat',
298
+ ... ocean_color='lightcyan'
299
+ ... )
300
+ """
301
+ from matplotlib import ticker as mticker
361
302
 
362
- # set extent
303
+ # Add map features
304
+ if show_land:
305
+ axes.add_feature(cfeature.LAND, facecolor=land_color)
306
+ if show_ocean:
307
+ axes.add_feature(cfeature.OCEAN, facecolor=ocean_color)
308
+ if show_coastline:
309
+ axes.add_feature(cfeature.COASTLINE, linewidth=coastline_linewidth)
310
+ if show_borders:
311
+ axes.add_feature(cfeature.BORDERS, linewidth=coastline_linewidth, linestyle=":")
312
+
313
+ # Setup coordinate formatting
314
+ lon_formatter = LongitudeFormatter(zero_direction_label=False, number_format=f".{tick_decimals}f")
315
+ lat_formatter = LatitudeFormatter(number_format=f".{tick_decimals}f")
316
+
317
+ # Handle gridlines and ticks
318
+ if show_gridlines:
319
+ # Add gridlines with labels
320
+ gl = axes.gridlines(crs=map_projection, draw_labels=show_labels, linewidth=grid_width, color=grid_color, alpha=grid_alpha, linestyle=grid_style)
321
+
322
+ # Configure label positions
323
+ gl.left_labels = left_labels
324
+ gl.bottom_labels = bottom_labels
325
+ gl.right_labels = right_labels
326
+ gl.top_labels = top_labels
327
+
328
+ # Set formatters
329
+ gl.xformatter = lon_formatter
330
+ gl.yformatter = lat_formatter
331
+
332
+ # Set custom tick positions if provided
333
+ if longitude_ticks is not None:
334
+ gl.xlocator = mticker.FixedLocator(np.array(longitude_ticks))
335
+ if latitude_ticks is not None:
336
+ gl.ylocator = mticker.FixedLocator(np.array(latitude_ticks))
337
+
338
+ elif show_labels:
339
+ # Add tick labels without gridlines
340
+ # Use current tick positions if not provided
341
+ if longitude_ticks is None:
342
+ longitude_ticks = axes.get_xticks()
343
+ if latitude_ticks is None:
344
+ latitude_ticks = axes.get_yticks()
345
+
346
+ # Set tick positions and formatters
347
+ axes.set_xticks(longitude_ticks, crs=map_projection)
348
+ axes.set_yticks(latitude_ticks, crs=map_projection)
349
+ axes.xaxis.set_major_formatter(lon_formatter)
350
+ axes.yaxis.set_major_formatter(lat_formatter)
351
+
352
+ # Set map extent if data is provided
363
353
  if longitude_data is not None and latitude_data is not None:
364
354
  lon_min, lon_max = np.nanmin(longitude_data), np.nanmax(longitude_data)
365
355
  lat_min, lat_max = np.nanmin(latitude_data), np.nanmax(latitude_data)
366
356
  axes.set_extent([lon_min, lon_max, lat_min, lat_max], crs=map_projection)
367
357
 
368
- # print("[green]Cartopy features added successfully.[/green]")
369
358
  return axes
370
359
 
371
360
 
372
361
  class MidpointNormalize(mpl.colors.Normalize):
373
- """Custom normalization class to center 0 value.
362
+ """Custom normalization class to center a specific value.
374
363
 
375
364
  Args:
376
- min_value (float, optional): Minimum data value. Defaults to None.
377
- max_value (float, optional): Maximum data value. Defaults to None.
378
- center_value (float, optional): Center value for normalization. Defaults to None.
379
- clip_values (bool, optional): Whether to clip data outside the range. Defaults to False.
365
+ vmin (float, optional): Minimum data value. Defaults to None.
366
+ vmax (float, optional): Maximum data value. Defaults to None.
367
+ vcenter (float, optional): Center value for normalization. Defaults to 0.
368
+ clip (bool, optional): Whether to clip data outside the range. Defaults to False.
380
369
 
381
370
  Example:
382
- >>> norm = MidpointNormalize(min_value=-2, max_value=1, center_value=0)
371
+ >>> norm = MidpointNormalize(vmin=-2, vmax=1, vcenter=0)
383
372
  """
384
373
 
385
- def __init__(self, min_value: float = None, max_value: float = None, center_value: float = None, clip_values: bool = False) -> None:
386
- self.vcenter = center_value
387
- super().__init__(min_value, max_value, clip_values)
374
+ def __init__(self, vmin: float = None, vmax: float = None, vcenter: float = 0, clip: bool = False) -> None:
375
+ self.vcenter = vcenter
376
+ super().__init__(vmin, vmax, clip)
388
377
 
389
- def __call__(self, input_values: np.ndarray, clip_values: bool = None) -> np.ma.MaskedArray:
378
+ def __call__(self, value: np.ndarray, clip: bool = None) -> np.ma.MaskedArray:
379
+ # Use the clip parameter from initialization if not provided
380
+ if clip is None:
381
+ clip = self.clip
382
+
390
383
  x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1.0]
391
- return np.ma.masked_array(np.interp(input_values, x, y, left=-np.inf, right=np.inf))
392
-
393
- def inverse(self, normalized_values: np.ndarray) -> np.ndarray:
384
+ result = np.interp(value, x, y)
385
+
386
+ # Apply clipping if requested
387
+ if clip:
388
+ result = np.clip(result, 0, 1)
389
+
390
+ return np.ma.masked_array(result)
391
+
392
+ def inverse(self, value: np.ndarray) -> np.ndarray:
394
393
  y, x = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
395
- return np.interp(normalized_values, x, y, left=-np.inf, right=np.inf)
396
-
397
- # print("[green]Midpoint normalization applied successfully.[/green]")
394
+ return np.interp(value, x, y)
398
395
 
399
396
 
400
397
  if __name__ == "__main__":
oafuncs/oa_file.py CHANGED
@@ -67,12 +67,16 @@ def link_file(source_pattern: str, destination: str) -> None:
67
67
  else:
68
68
  dst_file = destination
69
69
 
70
- os.makedirs(os.path.dirname(dst_file), exist_ok=True)
70
+ # Ensure destination directory exists
71
+ dst_dir = os.path.dirname(dst_file)
72
+ if dst_dir:
73
+ os.makedirs(dst_dir, exist_ok=True)
71
74
 
72
- if os.path.exists(dst_file):
75
+ # Remove existing file/link if it exists
76
+ if os.path.exists(dst_file) or os.path.islink(dst_file):
73
77
  os.remove(dst_file)
78
+
74
79
  os.symlink(src_file, dst_file)
75
-
76
80
  print(f"[green]Successfully created symbolic link:[/green] [bold]{src_file}[/bold] -> [bold]{dst_file}[/bold]")
77
81
  except Exception as e:
78
82
  print(f"[red]Failed to create symbolic link:[/red] [bold]{src_file}[/bold]. Error: {e}")
@@ -103,8 +107,12 @@ def copy_file(source_pattern: str, destination: str) -> None:
103
107
  else:
104
108
  dst_file = destination
105
109
 
106
- os.makedirs(os.path.dirname(dst_file), exist_ok=True)
110
+ # Ensure destination directory exists
111
+ dst_dir = os.path.dirname(dst_file)
112
+ if dst_dir:
113
+ os.makedirs(dst_dir, exist_ok=True)
107
114
 
115
+ # Remove existing destination if it exists
108
116
  if os.path.exists(dst_file):
109
117
  if os.path.isdir(dst_file):
110
118
  shutil.rmtree(dst_file)
@@ -147,15 +155,19 @@ def move_file(source_pattern: str, destination: str) -> None:
147
155
  else:
148
156
  dst_file = destination
149
157
 
150
- os.makedirs(os.path.dirname(dst_file), exist_ok=True)
158
+ # Ensure destination directory exists
159
+ dst_dir = os.path.dirname(dst_file)
160
+ if dst_dir:
161
+ os.makedirs(dst_dir, exist_ok=True)
151
162
 
163
+ # Remove existing destination if it exists
152
164
  if os.path.exists(dst_file):
153
165
  if os.path.isdir(dst_file):
154
166
  shutil.rmtree(dst_file)
155
167
  else:
156
168
  os.remove(dst_file)
169
+
157
170
  shutil.move(src_file, dst_file)
158
-
159
171
  print(f"[green]Successfully moved:[/green] [bold]{src_file}[/bold] -> [bold]{dst_file}[/bold]")
160
172
  except Exception as e:
161
173
  print(f"[red]Failed to move:[/red] [bold]{src_file}[/bold]. Error: {e}")
@@ -352,13 +364,13 @@ def mean_size(parent_dir: Union[str, os.PathLike], file_pattern: str, max_files:
352
364
  >>> avg_size = mean_size("/data/logs", "*.log", max_files=10, unit="MB")
353
365
  >>> print(f"Average log file size is {avg_size} MB")
354
366
  """
355
- # 获取文件列表
367
+ # Get file list
356
368
  flist = find_file(parent_dir, file_pattern)
357
369
  if not flist:
358
370
  print(f"[yellow]No files found matching pattern:[/yellow] [bold]{file_pattern}[/bold] in [bold]{parent_dir}[/bold]")
359
371
  return 0.0
360
372
 
361
- # 处理最大文件数限制
373
+ # Handle max_files limit
362
374
  if max_files is not None:
363
375
  try:
364
376
  max_files = int(max_files)
@@ -369,11 +381,11 @@ def mean_size(parent_dir: Union[str, os.PathLike], file_pattern: str, max_files:
369
381
  except (ValueError, TypeError):
370
382
  print(f"[yellow]Invalid max_files value:[/yellow] [bold]{max_files}[/bold], using all files")
371
383
 
372
- # 获取每个文件的大小并过滤掉大小为0的文件
384
+ # Get file sizes and filter out zero-size files
373
385
  size_list = [file_size(f, unit) for f in flist]
374
386
  valid_sizes = [size for size in size_list if size > 0]
375
387
 
376
- # 计算平均值
388
+ # Calculate average
377
389
  if valid_sizes:
378
390
  avg_size = sum(valid_sizes) / len(valid_sizes)
379
391
  if len(valid_sizes) < len(size_list):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.31
3
+ Version: 0.0.98.32
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -1,18 +1,18 @@
1
1
  oafuncs/__init__.py,sha256=T_-VtnWWllV3Q91twT5Yt2sUapeA051QbPNnBxmg9nw,1456
2
- oafuncs/oa_cmap.py,sha256=HZBqVM71Aw8pjLa8aaazoJT_huOTevoI7at5ZoKslK0,12996
2
+ oafuncs/oa_cmap.py,sha256=KKEM3Dx0RYYFdLaj0eX6p1iQ1IcKR0RZmt7a3bMGO3Y,13609
3
3
  oafuncs/oa_data.py,sha256=Wp7Yn6n-WNddAp1UtwK5CSEdGSrzzgOH5gtaOHBaxtw,8065
4
- oafuncs/oa_date.py,sha256=WhM6cyD4G3IeghjLTHhAMtlvJbA7kwQG2sHnxdTgyso,6303
5
- oafuncs/oa_draw.py,sha256=IaBGDx-EOxyMM2IuJ4zLZt6ruHHV5qFStPItmUOXoWk,17635
6
- oafuncs/oa_file.py,sha256=j9gXJgPOJsliu4IOUc4bc-luW4yBvQyNCEmMyDVjUwQ,16404
4
+ oafuncs/oa_date.py,sha256=aU2wVIWXyWoRiSQ9dg8sHvShFTxw86RrgbV3Q6tDjD4,6841
5
+ oafuncs/oa_draw.py,sha256=xry1Roty5P4LD_VXLSzEwfh_Yx3RwCLz-xib1H9MwmI,15172
6
+ oafuncs/oa_file.py,sha256=fLb0gRhq2AiPl-5ASDHMrx6Z267FmhqNcTV7CdCxTdI,16934
7
7
  oafuncs/oa_help.py,sha256=_4AZgRDq5Or0vauNvq5IDDHIBoBfdOQtzak-mG1wwAw,4537
8
8
  oafuncs/oa_nc.py,sha256=TMrrPBdPz1IJ-7dMOD_gzzTbG6FHSgm-ZfdlvoJDtcY,15245
9
9
  oafuncs/oa_python.py,sha256=NkopwkYFGSEuVljnTBvXCl6o2CeyRNBqRXSsUl3euEE,5192
10
10
  oafuncs/oa_tool.py,sha256=Zuaoa92wll0YqXGRf0oF_c7wlATtl7bvjCuLt9VLXp0,8046
11
11
  oafuncs/_data/hycom.png,sha256=MadKs6Gyj5n9-TOu7L4atQfTXtF9dvN9w-tdU9IfygI,10945710
12
12
  oafuncs/_data/oafuncs.png,sha256=o3VD7wm-kwDea5E98JqxXl04_78cBX7VcdUt7uQXGiU,3679898
13
- oafuncs/_script/cprogressbar.py,sha256=UIgGcLFs-6IgWlITuBLaQqrpt4OAK3Mst5RlCiNfZdQ,15772
13
+ oafuncs/_script/cprogressbar.py,sha256=nIOs42t6zURLTNjJglavqrI2TKO9GWD0AmZ_DOBmXDU,15949
14
14
  oafuncs/_script/data_interp.py,sha256=EiZbt6n5BEaRKcng88UgX7TFPhKE6TLVZniS01awXjg,5146
15
- oafuncs/_script/email.py,sha256=lL4HGKrr524-g0xLlgs-4u7x4-u7DtgNoD9AL8XJKj4,3058
15
+ oafuncs/_script/email.py,sha256=l5xDgdVj8O5V0J2SwjsHKdUuxOH2jZvwdMO_P0dImHU,2684
16
16
  oafuncs/_script/netcdf_merge.py,sha256=tM9ePqLiEsE7eIsNM5XjEYeXwxjYOdNz5ejnEuI7xKw,6066
17
17
  oafuncs/_script/netcdf_modify.py,sha256=sGRUYNhfGgf9JV70rnBzw3bzuTRSXzBTL_RMDnDPeLQ,4552
18
18
  oafuncs/_script/netcdf_write.py,sha256=GvyUyUhzMonzSp3y4pT8ZAfbQrsh5J3dLnmINYJKhuE,21422
@@ -22,7 +22,7 @@ oafuncs/_script/plot_dataset.py,sha256=4jhUZNIc2DLHnk5GH0rotzhOq0cAMmB7rhMJKorYO
22
22
  oafuncs/_script/replace_file_content.py,sha256=eCFZjnZcwyRvy6b4mmIfBna-kylSZTyJRfgXd6DdCjk,5982
23
23
  oafuncs/oa_down/User_Agent-list.txt,sha256=pHaMlElMvZ8TG4vf4BqkZYKqe0JIGkr4kCN0lM1Y9FQ,514295
24
24
  oafuncs/oa_down/__init__.py,sha256=kRX5eTUCbAiz3zTaQM1501paOYS_3fizDN4Pa0mtNUA,585
25
- oafuncs/oa_down/hycom_3hourly.py,sha256=R5fKfIcpNRuaQgPiov_hJRd8voWgAHVLWifAMzR6RQI,55075
25
+ oafuncs/oa_down/hycom_3hourly.py,sha256=pfUMZp_Tmfh3LbOV5Gd87998ZenuAFOAnxy1Vxi1NpM,55071
26
26
  oafuncs/oa_down/hycom_3hourly_proxy.py,sha256=1eaoJGI_m-7w4ZZ3n7NGxkZaeFdsm0d3U-hyw8RFNbc,54563
27
27
  oafuncs/oa_down/idm.py,sha256=4z5IvgfTyIKEI1kOtqXZwN7Jnfjwp6qDBOIoVyOLp0I,1823
28
28
  oafuncs/oa_down/literature.py,sha256=2bF9gSKQbzcci9LcKE81j8JEjIJwON7jbwQB3gDDA3E,11331
@@ -38,8 +38,8 @@ oafuncs/oa_sign/__init__.py,sha256=QKqTFrJDFK40C5uvk48GlRRbGFzO40rgkYwu6dYxatM,5
38
38
  oafuncs/oa_sign/meteorological.py,sha256=8091SHo2L8kl4dCFmmSH5NGVHDku5i5lSiLEG5DLnOQ,6489
39
39
  oafuncs/oa_sign/ocean.py,sha256=xrW-rWD7xBWsB5PuCyEwQ1Q_RDKq2KCLz-LOONHgldU,5932
40
40
  oafuncs/oa_sign/scientific.py,sha256=a4JxOBgm9vzNZKpJ_GQIQf7cokkraV5nh23HGbmTYKw,5064
41
- oafuncs-0.0.98.31.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
42
- oafuncs-0.0.98.31.dist-info/METADATA,sha256=BMgD2sAlLCubQ0xiFPtTKbrYVVSmpF4q2oZLYBsbMtA,4326
43
- oafuncs-0.0.98.31.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
44
- oafuncs-0.0.98.31.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
45
- oafuncs-0.0.98.31.dist-info/RECORD,,
41
+ oafuncs-0.0.98.32.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
42
+ oafuncs-0.0.98.32.dist-info/METADATA,sha256=qCDKj6VKcdl60LTnWfzGqEDg0opylNPBgfhs6dHddfQ,4326
43
+ oafuncs-0.0.98.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ oafuncs-0.0.98.32.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
45
+ oafuncs-0.0.98.32.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5