oafuncs 0.0.97__tar.gz → 0.0.97.1__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 (45) hide show
  1. oafuncs-0.0.97.1/MANIFEST.in +4 -0
  2. {oafuncs-0.0.97/oafuncs.egg-info → oafuncs-0.0.97.1}/PKG-INFO +1 -3
  3. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/__init__.py +7 -4
  4. oafuncs-0.0.97.1/oafuncs/_script/__init__.py +27 -0
  5. oafuncs-0.0.97.1/oafuncs/_script/plot_dataset.py +299 -0
  6. oafuncs-0.0.97.1/oafuncs/data_store/hycom_3hourly.png +0 -0
  7. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_cmap.py +5 -3
  8. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_data.py +118 -6
  9. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_file.py +22 -18
  10. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_nc.py +59 -18
  11. {oafuncs-0.0.97 → oafuncs-0.0.97.1/oafuncs.egg-info}/PKG-INFO +1 -3
  12. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs.egg-info/SOURCES.txt +3 -0
  13. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs.egg-info/requires.txt +0 -1
  14. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/setup.py +27 -32
  15. oafuncs-0.0.97/MANIFEST.in +0 -2
  16. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/LICENSE.txt +0 -0
  17. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/README.md +0 -0
  18. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/data_store/OAFuncs.png +0 -0
  19. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/User_Agent-list.txt +0 -0
  20. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/__init__.py +0 -0
  21. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/hycom_3hourly.py +0 -0
  22. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/hycom_3hourly_20250129.py +0 -0
  23. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/idm.py +0 -0
  24. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/literature.py +0 -0
  25. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/test_ua.py +0 -0
  26. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_down/user_agent.py +0 -0
  27. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_draw.py +0 -0
  28. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_help.py +0 -0
  29. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_model/__init__.py +0 -0
  30. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_model/roms/__init__.py +0 -0
  31. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_model/roms/test.py +0 -0
  32. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_model/wrf/__init__.py +0 -0
  33. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_model/wrf/little_r.py +0 -0
  34. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_python.py +0 -0
  35. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_sign/__init__.py +0 -0
  36. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_sign/meteorological.py +0 -0
  37. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_sign/ocean.py +0 -0
  38. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_sign/scientific.py +0 -0
  39. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_tool/__init__.py +0 -0
  40. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_tool/email.py +0 -0
  41. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_tool/parallel.py +0 -0
  42. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs/oa_tool/time.py +0 -0
  43. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs.egg-info/dependency_links.txt +0 -0
  44. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/oafuncs.egg-info/top_level.txt +0 -0
  45. {oafuncs-0.0.97 → oafuncs-0.0.97.1}/setup.cfg +0 -0
@@ -0,0 +1,4 @@
1
+ include LICENSE.txt
2
+ include README.md
3
+ recursive-include oafuncs/data_store *
4
+ recursive-include oafuncs/oa_down *.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: oafuncs
3
- Version: 0.0.97
3
+ Version: 0.0.97.1
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -9,7 +9,6 @@ License: MIT
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
@@ -33,7 +32,6 @@ Requires-Dist: geopandas
33
32
  Requires-Dist: Cartopy
34
33
  Requires-Dist: rasterio
35
34
  Requires-Dist: salem
36
- Requires-Dist: calendar
37
35
  Dynamic: author
38
36
  Dynamic: author-email
39
37
  Dynamic: classifier
@@ -14,7 +14,6 @@ Python Version: 3.12
14
14
  """
15
15
 
16
16
 
17
-
18
17
  # 会导致OAFuncs直接导入所有函数,不符合模块化设计
19
18
  # from oafuncs.oa_s.oa_cmap import *
20
19
  # from oafuncs.oa_s.oa_data import *
@@ -35,6 +34,10 @@ from .oa_down import *
35
34
  from .oa_draw import *
36
35
  from .oa_file import *
37
36
  from .oa_help import *
37
+
38
+ # ------------------- 2024-12-13 12:31:06 -------------------
39
+ # path: My_Funcs/OAFuncs/oafuncs/oa_model/
40
+ from .oa_model import *
38
41
  from .oa_nc import *
39
42
  from .oa_python import *
40
43
 
@@ -45,7 +48,7 @@ from .oa_sign import *
45
48
  # ------------------- 2024-12-13 12:31:06 -------------------
46
49
  # path: My_Funcs/OAFuncs/oafuncs/oa_tool/
47
50
  from .oa_tool import *
48
- # ------------------- 2024-12-13 12:31:06 -------------------
49
- # path: My_Funcs/OAFuncs/oafuncs/oa_model/
50
- from .oa_model import *
51
51
  # ------------------- 2025-03-09 16:28:01 -------------------
52
+ # path: My_Funcs/OAFuncs/oafuncs/_script/
53
+ from ._script import *
54
+ # ------------------- 2025-03-16 15:56:01 -------------------
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """
4
+ Author: Liu Kun && 16031215@qq.com
5
+ Date: 2025-03-13 15:26:15
6
+ LastEditors: Liu Kun && 16031215@qq.com
7
+ LastEditTime: 2025-03-13 15:26:18
8
+ FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_script\\__init__.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
+ # 会导致OAFuncs直接导入所有函数,不符合模块化设计
19
+ # from oafuncs.oa_s.oa_cmap import *
20
+ # from oafuncs.oa_s.oa_data import *
21
+ # from oafuncs.oa_s.oa_draw import *
22
+ # from oafuncs.oa_s.oa_file import *
23
+ # from oafuncs.oa_s.oa_help import *
24
+ # from oafuncs.oa_s.oa_nc import *
25
+ # from oafuncs.oa_s.oa_python import *
26
+
27
+ from .plot_dataset import func_plot_dataset
@@ -0,0 +1,299 @@
1
+ import os
2
+ from typing import Optional, Tuple
3
+
4
+ import matplotlib as mpl
5
+
6
+ mpl.use("Agg") # Use non-interactive backend
7
+
8
+ import cftime
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+ from rich import print
12
+ import cartopy.crs as ccrs
13
+ import xarray as xr
14
+
15
+ import oafuncs
16
+
17
+
18
+ def plot_1d(data: xr.DataArray, output_path: str, x_dim: str, y_dim: str, z_dim: str, t_dim: str) -> None:
19
+ """Plot 1D data."""
20
+ plt.figure(figsize=(10, 6))
21
+
22
+ # Handle time dimension
23
+ if t_dim in data.dims and isinstance(data[t_dim].values[0], cftime.datetime):
24
+ try:
25
+ data[t_dim] = data.indexes[t_dim].to_datetimeindex()
26
+ except (AttributeError, ValueError, TypeError) as e:
27
+ print(f"Warning: Could not convert {t_dim} to datetime index: {e}")
28
+
29
+ # Determine X axis data
30
+ x, x_label = determine_x_axis(data, x_dim, y_dim, z_dim, t_dim)
31
+
32
+ y = data.values
33
+ plt.plot(x, y, linewidth=2)
34
+
35
+ # Add chart info
36
+ long_name = getattr(data, "long_name", "No long_name")
37
+ units = getattr(data, "units", "")
38
+ plt.title(f"{data.name} | {long_name}", fontsize=12)
39
+ plt.xlabel(x_label)
40
+ plt.ylabel(f"{data.name} ({units})" if units else data.name)
41
+
42
+ plt.grid(True, linestyle="--", alpha=0.7)
43
+ plt.tight_layout()
44
+
45
+ # Save image
46
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
47
+ plt.savefig(output_path, bbox_inches="tight", dpi=600)
48
+ plt.clf()
49
+ plt.close()
50
+
51
+
52
+ def determine_x_axis(data: xr.DataArray, x_dim: str, y_dim: str, z_dim: str, t_dim: str) -> Tuple[np.ndarray, str]:
53
+ """Determine the X axis data and label."""
54
+ if x_dim in data.dims:
55
+ return data[x_dim].values, x_dim
56
+ elif y_dim in data.dims:
57
+ return data[y_dim].values, y_dim
58
+ elif z_dim in data.dims:
59
+ return data[z_dim].values, z_dim
60
+ elif t_dim in data.dims:
61
+ return data[t_dim].values, t_dim
62
+ else:
63
+ return np.arange(len(data)), "Index"
64
+
65
+
66
+ def plot_2d(data: xr.DataArray, output_path: str, data_range: Optional[Tuple[float, float]], x_dim: str, y_dim: str, t_dim: str, plot_type: str) -> bool:
67
+ """Plot 2D data."""
68
+ if x_dim in data.dims and y_dim in data.dims and x_dim.lower() in ["lon", "longitude"] and y_dim.lower() in ["lat", "latitude"]:
69
+ lon_range = data[x_dim].values
70
+ lat_range = data[y_dim].values
71
+ lon_lat_ratio = np.abs(np.max(lon_range) - np.min(lon_range)) / (np.max(lat_range) - np.min(lat_range))
72
+ figsize = (10, 10 / lon_lat_ratio)
73
+ fig, ax = plt.subplots(figsize=figsize, subplot_kw={"projection": ccrs.PlateCarree()})
74
+ oafuncs.oa_draw.add_cartopy(ax, lon_range, lat_range)
75
+ else:
76
+ fig, ax = plt.subplots(figsize=(10, 8))
77
+
78
+ # Handle time dimension
79
+ if t_dim in data.dims and isinstance(data[t_dim].values[0], cftime.datetime):
80
+ try:
81
+ data[t_dim] = data.indexes[t_dim].to_datetimeindex()
82
+ except (AttributeError, ValueError, TypeError) as e:
83
+ print(f"Warning: Could not convert {t_dim} to datetime index: {e}")
84
+
85
+ # Check for valid data
86
+ if np.all(np.isnan(data.values)) or data.size == 0:
87
+ print(f"Skipping {data.name}: All values are NaN or empty")
88
+ plt.close()
89
+ return False
90
+
91
+ data_range = calculate_data_range(data, data_range)
92
+
93
+ if data_range is None:
94
+ print(f"Skipping {data.name} due to all NaN values")
95
+ plt.close()
96
+ return False
97
+
98
+ # Select appropriate colormap and levels
99
+ cmap, norm, levels = select_colormap_and_levels(data_range, plot_type)
100
+
101
+ mappable = None
102
+ try:
103
+ if plot_type == "contourf":
104
+ if np.ptp(data.values) < 1e-10 and not np.all(np.isnan(data.values)):
105
+ print(f"Warning: {data.name} has very little variation. Using imshow instead.")
106
+ mappable = ax.imshow(data.values, cmap=cmap, aspect="auto", interpolation="none")
107
+ colorbar = plt.colorbar(mappable, ax=ax)
108
+ else:
109
+ mappable = ax.contourf(data[x_dim], data[y_dim], data.values, levels=levels, cmap=cmap, norm=norm)
110
+ colorbar = plt.colorbar(mappable, ax=ax)
111
+ elif plot_type == "contour":
112
+ if np.ptp(data.values) < 1e-10 and not np.all(np.isnan(data.values)):
113
+ print(f"Warning: {data.name} has very little variation. Using imshow instead.")
114
+ mappable = ax.imshow(data.values, cmap=cmap, aspect="auto", interpolation="none")
115
+ colorbar = plt.colorbar(mappable, ax=ax)
116
+ else:
117
+ mappable = ax.contour(data[x_dim], data[y_dim], data.values, levels=levels, cmap=cmap, norm=norm)
118
+ ax.clabel(mappable, inline=True, fontsize=8, fmt="%1.1f")
119
+ colorbar = plt.colorbar(mappable, ax=ax)
120
+ except (ValueError, TypeError) as e:
121
+ print(f"Warning: Could not plot with specified parameters: {e}. Trying simplified parameters.")
122
+ try:
123
+ mappable = data.plot(ax=ax, cmap=cmap, add_colorbar=False)
124
+ colorbar = plt.colorbar(mappable, ax=ax)
125
+ except Exception as e2:
126
+ print(f"Error plotting {data.name}: {e2}")
127
+ plt.figure(figsize=(10, 8))
128
+ mappable = ax.imshow(data.values, cmap="viridis", aspect="auto")
129
+ colorbar = plt.colorbar(mappable, ax=ax, label=getattr(data, "units", ""))
130
+ plt.title(f"{data.name} | {getattr(data, 'long_name', 'No long_name')} (basic plot)", fontsize=12)
131
+ plt.tight_layout()
132
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
133
+ plt.savefig(output_path, bbox_inches="tight", dpi=600)
134
+ plt.close()
135
+ return True
136
+
137
+ plt.title(f"{data.name} | {getattr(data, 'long_name', 'No long_name')}", fontsize=12)
138
+ units = getattr(data, "units", "")
139
+ if units and colorbar:
140
+ colorbar.set_label(units)
141
+
142
+ plt.tight_layout()
143
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
144
+ plt.savefig(output_path, bbox_inches="tight", dpi=600)
145
+ plt.close()
146
+ return True
147
+
148
+
149
+ def calculate_data_range(data: xr.DataArray, data_range: Optional[Tuple[float, float]]) -> Optional[Tuple[float, float]]:
150
+ """Calculate the data range, ignoring extreme outliers."""
151
+ if data_range is None:
152
+ flat_data = data.values.flatten()
153
+ if flat_data.size == 0:
154
+ return None
155
+ valid_data = flat_data[~np.isnan(flat_data)]
156
+ if len(valid_data) == 0:
157
+ return None
158
+ low, high = np.percentile(valid_data, [0.5, 99.5])
159
+ filtered_data = valid_data[(valid_data >= low) & (valid_data <= high)]
160
+ if len(filtered_data) > 0:
161
+ data_range = (np.min(filtered_data), np.max(filtered_data))
162
+ else:
163
+ data_range = (np.nanmin(valid_data), np.nanmax(valid_data))
164
+ if abs(data_range[1] - data_range[0]) < 1e-10:
165
+ mean = (data_range[0] + data_range[1]) / 2
166
+ data_range = (mean - 1e-10 if mean != 0 else -1e-10, mean + 1e-10 if mean != 0 else 1e-10)
167
+ return data_range
168
+
169
+
170
+ def select_colormap_and_levels(data_range: Tuple[float, float], plot_type: str) -> Tuple[mpl.colors.Colormap, mpl.colors.Normalize, np.ndarray]:
171
+ """Select colormap and levels based on data range."""
172
+ if plot_type == "contour":
173
+ # For contour plots, use fewer levels
174
+ num_levels = 10
175
+ else:
176
+ # For filled contour plots, use more levels
177
+ num_levels = 128
178
+
179
+ if data_range[0] * data_range[1] < 0:
180
+ cmap = oafuncs.oa_cmap.get("diverging_1")
181
+ bdy = max(abs(data_range[0]), abs(data_range[1]))
182
+ norm = mpl.colors.TwoSlopeNorm(vmin=-bdy, vcenter=0, vmax=bdy)
183
+ levels = np.linspace(-bdy, bdy, num_levels)
184
+ else:
185
+ cmap = oafuncs.oa_cmap.get("cool_1") if data_range[0] < 0 else oafuncs.oa_cmap.get("warm_1")
186
+ norm = mpl.colors.Normalize(vmin=data_range[0], vmax=data_range[1])
187
+ levels = np.linspace(data_range[0], data_range[1], num_levels)
188
+
189
+ if np.any(np.diff(levels) <= 0):
190
+ levels = np.linspace(data_range[0], data_range[1], 10)
191
+ return cmap, norm, levels
192
+
193
+
194
+ def process_variable(var: str, data: xr.DataArray, dims: int, dims_name: Tuple[str, ...], output_dir: str, x_dim: str, y_dim: str, z_dim: str, t_dim: str, fixed_colorscale: bool, plot_type: str) -> None:
195
+ """Process a single variable."""
196
+ valid_dims = {x_dim, y_dim, z_dim, t_dim}
197
+ if not set(dims_name).issubset(valid_dims):
198
+ print(f"Skipping {var} due to unsupported dimensions: {dims_name}")
199
+ return
200
+
201
+ # Process 1D data
202
+ if dims == 1:
203
+ if np.issubdtype(data.dtype, np.character):
204
+ print(f"Skipping {var} due to character data type")
205
+ return
206
+ plot_1d(data, os.path.join(output_dir, f"{var}.png"), x_dim, y_dim, z_dim, t_dim)
207
+ print(f"{var}.png")
208
+ return
209
+
210
+ # Compute global data range for fixed colorscale
211
+ global_data_range = None
212
+ if dims >= 2 and fixed_colorscale:
213
+ global_data_range = calculate_data_range(data, None)
214
+ if global_data_range is None:
215
+ print(f"Skipping {var} due to no valid data")
216
+ return
217
+ print(f"Fixed colorscale range: {global_data_range}")
218
+
219
+ # Process 2D data
220
+ if dims == 2:
221
+ success = plot_2d(data, os.path.join(output_dir, f"{var}.png"), global_data_range, x_dim, y_dim, t_dim, plot_type)
222
+ if success:
223
+ print(f"{var}.png")
224
+
225
+ # Process 3D data
226
+ if dims == 3:
227
+ for i in range(data.shape[0]):
228
+ for attempt in range(10):
229
+ try:
230
+ if data[i].values.size == 0:
231
+ print(f"Skipped {var}_{dims_name[0]}-{i} (empty data)")
232
+ break
233
+ success = plot_2d(data[i], os.path.join(output_dir, f"{var}_{dims_name[0]}-{i}.png"), global_data_range, x_dim, y_dim, t_dim, plot_type)
234
+ if success:
235
+ print(f"{var}_{dims_name[0]}-{i}.png")
236
+ else:
237
+ print(f"Skipped {var}_{dims_name[0]}-{i} (invalid data)")
238
+ break
239
+ except Exception as e:
240
+ if attempt < 9:
241
+ print(f"Retrying {var}_{dims_name[0]}-{i} (attempt {attempt + 1})")
242
+ else:
243
+ print(f"Error processing {var}_{dims_name[0]}-{i}: {e}")
244
+
245
+ # Process 4D data
246
+ if dims == 4:
247
+ for i in range(data.shape[0]):
248
+ for j in range(data.shape[1]):
249
+ for attempt in range(3):
250
+ try:
251
+ if data[i, j].values.size == 0:
252
+ print(f"Skipped {var}_{dims_name[0]}-{i}_{dims_name[1]}-{j} (empty data)")
253
+ break
254
+ success = plot_2d(data[i, j], os.path.join(output_dir, f"{var}_{dims_name[0]}-{i}_{dims_name[1]}-{j}.png"), global_data_range, x_dim, y_dim, t_dim, plot_type)
255
+ if success:
256
+ print(f"{var}_{dims_name[0]}-{i}_{dims_name[1]}-{j}.png")
257
+ else:
258
+ print(f"Skipped {var}_{dims_name[0]}-{i}_{dims_name[1]}-{j} (invalid data)")
259
+ break
260
+ except Exception as e:
261
+ if attempt < 2:
262
+ print(f"Retrying {var}_{dims_name[0]}-{i}_{dims_name[1]}-{j} (attempt {attempt + 1})")
263
+ else:
264
+ print(f"Error processing {var}_{dims_name[0]}-{i}_{dims_name[1]}-{j}: {e}")
265
+
266
+
267
+ def func_plot_dataset(ds_in: xr.Dataset, output_dir: str, xyzt_dims: Tuple[str, str, str, str] = ("longitude", "latitude", "level", "time"), plot_type: str = "contourf", fixed_colorscale: bool = False) -> None:
268
+ """Plot variables from a NetCDF file and save the plots to the specified directory."""
269
+ os.makedirs(output_dir, exist_ok=True)
270
+ x_dim, y_dim, z_dim, t_dim = xyzt_dims
271
+
272
+ # Main processing function
273
+ try:
274
+ ds = ds_in
275
+ varlist = list(ds.data_vars)
276
+ print(f"Found {len(varlist)} variables in dataset")
277
+
278
+ for var in varlist:
279
+ print("=" * 120)
280
+ print(f"Processing: {var}")
281
+ data = ds[var]
282
+ dims = len(data.shape)
283
+ dims_name = data.dims
284
+ try:
285
+ process_variable(var, data, dims, dims_name, output_dir, x_dim, y_dim, z_dim, t_dim, fixed_colorscale, plot_type)
286
+ except Exception as e:
287
+ print(f"Error processing variable {var}: {e}")
288
+
289
+ except Exception as e:
290
+ print(f"Error processing dataset: {e}")
291
+ finally:
292
+ if "ds" in locals():
293
+ ds.close()
294
+ print("Dataset closed")
295
+
296
+
297
+ if __name__ == "__main__":
298
+ pass
299
+ # func_plot_dataset(ds, output_dir, xyzt_dims=("longitude", "latitude", "level", "time"), plot_type="contourf", fixed_colorscale=False)
@@ -143,14 +143,14 @@ def get(cmap_name=None, query=False):
143
143
  Example:
144
144
  cmap = get('viridis')
145
145
  cmap = get('diverging_1')
146
- cmap = get('cold_1')
146
+ cmap = get('cool_1')
147
147
  cmap = get('warm_1')
148
148
  cmap = get('colorful_1')
149
149
  """
150
150
 
151
151
  my_cmap_dict = {
152
152
  "diverging_1": create(["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"]),
153
- "cold_1": create(["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC"]),
153
+ "cool_1": create(["#4e00b3", "#0000FF", "#00c0ff", "#a1d3ff", "#DCDCDC"]),
154
154
  "warm_1": create(["#DCDCDC", "#FFD39B", "#FF8247", "#FF0000", "#FF5F9E"]),
155
155
  # "land_1": create_custom(["#3E6436", "#678A59", "#91A176", "#B8A87D", "#D9CBB2"], under="#A6CEE3", over="#FFFFFF"),
156
156
  # "ocean_1": create_custom(["#126697", "#2D88B3", "#4EA1C9", "#78B9D8", "#A6CEE3"], under="#8470FF", over="#3E6436"),
@@ -191,7 +191,9 @@ def get(cmap_name=None, query=False):
191
191
  try:
192
192
  return mpl.colormaps.get_cmap(cmap_name)
193
193
  except ValueError:
194
- raise ValueError(f"Unknown cmap name: {cmap_name}")
194
+ # raise ValueError(f"Unknown cmap name: {cmap_name}")
195
+ print(f"Unknown cmap name: {cmap_name}\nNow return 'rainbow' as default.")
196
+ return mpl.colormaps.get_cmap("rainbow")
195
197
 
196
198
 
197
199
  if __name__ == "__main__":
@@ -18,11 +18,12 @@ import multiprocessing as mp
18
18
  from concurrent.futures import ThreadPoolExecutor
19
19
 
20
20
  import numpy as np
21
+ import salem
21
22
  import xarray as xr
22
23
  from scipy.interpolate import griddata
23
- import salem
24
+ from scipy.interpolate import interp1d
24
25
 
25
- __all__ = ["interp_2d", "ensure_list", "mask_shapefile"]
26
+ __all__ = ["interp_along_dim", "interp_2d", "ensure_list", "mask_shapefile"]
26
27
 
27
28
 
28
29
  def ensure_list(input_data):
@@ -45,15 +46,126 @@ def ensure_list(input_data):
45
46
  return [str(input_data)]
46
47
 
47
48
 
49
+ def interp_along_dim(tgt_coords, src_coords, src_data, axis=-1, interp_method="linear", extrap_method="linear"):
50
+ """
51
+ 在指定维度上执行插值和外插操作。
52
+
53
+ Parameters:
54
+ -----------
55
+ tgt_coords: 1d array
56
+ 目标坐标点数组,必须是一维数组。
57
+
58
+ src_coords: 1d or nd array
59
+ 源坐标点数组。可以是一维数组(将被广播到与src_data匹配的形状)或与src_data相同形状的多维数组。
60
+
61
+ src_data: nd array
62
+ 源数据数组,包含要从src_coords插值到tgt_coords的现象值。
63
+
64
+ axis: int (default -1)
65
+ 要在src_data上执行插值的轴。默认为最后一个轴。
66
+
67
+ interp_method: str (default "linear")
68
+ 核心插值方法。
69
+ 可选值包括:
70
+ - "linear": 线性插值(默认)
71
+ - "nearest": 最近邻插值
72
+ - "zero": 零阶插值
73
+ - "slinear": 样条一阶插值
74
+ - "quadratic": 二阶插值
75
+ - "cubic": 三阶插值
76
+ - "previous": 使用前一个点的值
77
+ - "next": 使用后一个点的值
78
+ 更多选项参考scipy.interpolate.interp1d的kind参数。
79
+
80
+ extrap_method: str (default "linear")
81
+ 核心外插方法,用于处理超出源坐标范围的目标坐标点。
82
+ 支持与interp_method相同的选项:
83
+ - "linear": 线性外插(默认)
84
+ - "nearest": 最近邻外插
85
+ - "zero": 零阶外插
86
+ - "slinear": 样条一阶外插
87
+ - "quadratic": 二阶外插
88
+ - "cubic": 三阶外插
89
+ - "previous": 使用最近的前一个点的值
90
+ - "next": 使用最近的后一个点的值
91
+
92
+ Returns:
93
+ --------
94
+ array
95
+ 插值后的数据数组,形状将与src_data相同,但在axis轴上长度为len(tgt_coords)。
96
+
97
+ Examples:
98
+ ---------
99
+ 1D插值示例:
100
+ >>> tgt_coords = np.array([1, 2, 3, 4])
101
+ >>> src_coords = np.array([0, 1, 2, 3, 4, 5])
102
+ >>> src_data = np.array([0, 1, 4, 9, 16, 25])
103
+ >>> interp_along_dim(tgt_coords, src_coords, src_data)
104
+ array([ 1., 4., 9., 16.])
105
+
106
+ 多维插值示例:
107
+ >>> src_data = np.array([[0, 1, 4], [10, 20, 30]])
108
+ >>> interp_along_dim(np.array([0.5, 1.5]), np.array([0, 1, 2]), src_data, axis=1)
109
+ array([[ 0.5, 2.5],
110
+ [15. , 25. ]])
111
+ """
112
+ tgt_coords = np.asarray(tgt_coords)
113
+ if tgt_coords.ndim != 1:
114
+ raise ValueError("tgt_coords must be a 1d array.")
115
+
116
+ src_coords = np.asarray(src_coords)
117
+ src_data = np.asarray(src_data)
118
+
119
+ # 处理1维的简单情况
120
+ if src_data.ndim == 1 and src_coords.ndim == 1:
121
+ if len(src_coords) != len(src_data):
122
+ raise ValueError("For 1D data, src_coords and src_data must have the same length")
123
+
124
+ interpolator = interp1d(src_coords, src_data, kind=interp_method, fill_value="extrapolate", bounds_error=False)
125
+ return interpolator(tgt_coords)
126
+
127
+ # 多维情况的处理
128
+ if src_coords.ndim == 1:
129
+ # Expand src_coords to match src_data dimensions along the specified axis
130
+ shape = [1] * src_data.ndim
131
+ shape[axis] = src_coords.shape[0]
132
+ src_coords = np.reshape(src_coords, shape)
133
+ src_coords = np.broadcast_to(src_coords, src_data.shape)
134
+ elif src_coords.shape != src_data.shape:
135
+ raise ValueError("src_coords and src_data must have the same shape.")
136
+
137
+ def apply_interp_extrap(arr):
138
+ xp = np.moveaxis(src_coords, axis, 0)
139
+ # 根据维度正确获取坐标
140
+ if xp.ndim > 1:
141
+ xp = xp[:, 0] # 多维情况
142
+ else:
143
+ xp = xp # 1维情况
144
+
145
+ arr = np.moveaxis(arr, axis, 0)
146
+ interpolator = interp1d(xp, arr, kind=interp_method, fill_value="extrapolate", bounds_error=False)
147
+ interpolated = interpolator(tgt_coords)
148
+ if extrap_method != interp_method:
149
+ mask_extrap = (tgt_coords < xp.min()) | (tgt_coords > xp.max())
150
+ if np.any(mask_extrap):
151
+ extrap_interpolator = interp1d(xp, arr, kind=extrap_method, fill_value="extrapolate", bounds_error=False)
152
+ interpolated[mask_extrap] = extrap_interpolator(tgt_coords[mask_extrap])
153
+ return np.moveaxis(interpolated, 0, axis)
154
+
155
+ result = np.apply_along_axis(apply_interp_extrap, axis, src_data)
156
+
157
+ return result
158
+
159
+
48
160
  def interp_2d(target_x, target_y, origin_x, origin_y, data, method="linear", parallel=True):
49
161
  """
50
162
  Perform 2D interpolation on the last two dimensions of a multi-dimensional array.
51
163
 
52
164
  Parameters:
53
- - target_x (array-like): 1D array of target grid's x-coordinates.
54
- - target_y (array-like): 1D array of target grid's y-coordinates.
55
- - origin_x (array-like): 1D array of original grid's x-coordinates.
56
- - origin_y (array-like): 1D array of original grid's y-coordinates.
165
+ - target_x (array-like): 1D or 2D array of target grid's x-coordinates.
166
+ - target_y (array-like): 1D or 2D array of target grid's y-coordinates.
167
+ - origin_x (array-like): 1D or 2D array of original grid's x-coordinates.
168
+ - origin_y (array-like): 1D or 2D array of original grid's y-coordinates.
57
169
  - data (numpy.ndarray): Multi-dimensional array where the last two dimensions correspond to the original grid.
58
170
  - method (str, optional): Interpolation method, default is 'linear'. Other options include 'nearest', 'cubic', etc.
59
171
  - parallel (bool, optional): Flag to enable parallel processing. Default is True.
@@ -75,7 +75,7 @@ def link_file(src_pattern, dst):
75
75
  # 使用glob.glob来处理可能包含通配符的src
76
76
  src_files = glob.glob(src_pattern)
77
77
  if not src_files:
78
- raise FileNotFoundError("文件不存在: {}".format(src_pattern))
78
+ raise FileNotFoundError("File does not exist: {}".format(src_pattern))
79
79
 
80
80
  # 判断dst是路径还是包含文件名的路径
81
81
  if os.path.isdir(dst):
@@ -87,7 +87,8 @@ def link_file(src_pattern, dst):
87
87
  if os.path.exists(dst_file):
88
88
  os.remove(dst_file)
89
89
  os.symlink(src_file, dst_file)
90
- print(f"创建符号链接: {src_file} -> {dst_file}")
90
+ # print(f"创建符号链接: {src_file} -> {dst_file}")
91
+ print(f"Create a symbolic link: {src_file} -> {dst_file}")
91
92
  else:
92
93
  # 如果dst包含文件名,则创建链接后重命名
93
94
  dst_dir = os.path.dirname(dst)
@@ -98,7 +99,8 @@ def link_file(src_pattern, dst):
98
99
  if os.path.exists(dst_file):
99
100
  os.remove(dst_file)
100
101
  os.symlink(src_file, dst_file)
101
- print(f"创建符号链接并重命名: {src_file} -> {dst_file}")
102
+ # print(f"创建符号链接并重命名: {src_file} -> {dst_file}")
103
+ print(f"Create a symbolic link and rename: {src_file} -> {dst_file}")
102
104
 
103
105
 
104
106
  # ** 复制文件或目录,支持通配符
@@ -115,7 +117,7 @@ def copy_file(src_pattern, dst):
115
117
  # 使用glob.glob来处理可能包含通配符的src
116
118
  src_files = glob.glob(src_pattern)
117
119
  if not src_files:
118
- raise FileNotFoundError("文件不存在: {}".format(src_pattern))
120
+ raise FileNotFoundError("File does not exist: {}".format(src_pattern))
119
121
 
120
122
  # 判断dst是路径还是包含文件名的路径
121
123
  if os.path.isdir(dst):
@@ -133,7 +135,7 @@ def copy_file(src_pattern, dst):
133
135
  shutil.copytree(src_file, dst_file, symlinks=True)
134
136
  else:
135
137
  shutil.copy2(src_file, dst_file)
136
- print(f"复制文件或目录: {src_file} -> {dst_file}")
138
+ print(f"Copy file or directory: {src_file} -> {dst_file}")
137
139
  else:
138
140
  # 如果dst包含文件名,则复制后重命名
139
141
  dst_dir = os.path.dirname(dst)
@@ -150,7 +152,7 @@ def copy_file(src_pattern, dst):
150
152
  shutil.copytree(src_file, dst_file, symlinks=True)
151
153
  else:
152
154
  shutil.copy2(src_file, dst_file)
153
- print(f"复制文件或目录并重命名: {src_file} -> {dst_file}")
155
+ print(f"Copy and rename file or directory: {src_file} -> {dst_file}")
154
156
 
155
157
 
156
158
  # ** 重命名文件,支持通配符
@@ -158,7 +160,7 @@ def rename_file(directory, old_str, new_str):
158
160
  """
159
161
  # 描述:重命名目录下的文件,支持通配符
160
162
  # 使用示例
161
- directory_path = r"E:\windfarm\CROCO_FILES"
163
+ directory_path = r"E:\\windfarm\\CROCO_FILES"
162
164
  old_str = "croco"
163
165
  new_str = "roms"
164
166
  rename_file(directory_path, old_str, new_str)
@@ -187,7 +189,7 @@ def rename_file(directory, old_str, new_str):
187
189
 
188
190
  # 重命名文件
189
191
  os.rename(old_path, new_path)
190
- print(f"重命名文件: {old_path} -> {new_path}")
192
+ print(f"Rename file: {old_path} -> {new_path}")
191
193
 
192
194
 
193
195
  # ** 创建子文件夹(可选清空)
@@ -195,7 +197,7 @@ def make_folder(rootpath=None, folder_name=None, clear=False) -> str:
195
197
  """
196
198
  # 描述:创建子文件夹(可选清空)
197
199
  # 使用示例
198
- rootpath = r'E:\Data\2024\09\17'
200
+ rootpath = r'E:\\Data\\2024\\09\\17'
199
201
  folder_name = 'var1'
200
202
  newpath = make_folder(rootpath, folder_name, clear=1)
201
203
  param {*} rootpath # 根目录
@@ -223,7 +225,7 @@ def make_dir(directory):
223
225
  None
224
226
 
225
227
  Example:
226
- make_dir(r"E:\Data\2024\09\17\var1")
228
+ make_dir(r"E:\\Data\\2024\\09\\17\\var1")
227
229
  """
228
230
  directory = str(directory)
229
231
  if os.path.exists(directory):
@@ -239,7 +241,7 @@ def clear_folder(folder_path):
239
241
  """
240
242
  # 描述:清空文件夹
241
243
  # 使用示例
242
- clear_folder(r'E:\Data\2024\09\17\var1')
244
+ clear_folder(r'E:\\Data\\2024\\09\\17\\var1')
243
245
  param {*} folder_path # 文件夹路径
244
246
  """
245
247
  folder_path = str(folder_path)
@@ -253,9 +255,11 @@ def clear_folder(folder_path):
253
255
  os.unlink(file_path) # 删除文件或链接
254
256
  elif os.path.isdir(file_path):
255
257
  shutil.rmtree(file_path) # 删除子文件夹
256
- print(f"成功清空文件夹: {folder_path}")
258
+ # print(f"成功清空文件夹: {folder_path}")
259
+ print(f"Successfully cleared the folder: {folder_path}")
257
260
  except Exception as e:
258
- print(f"清空文件夹失败: {folder_path}")
261
+ # print(f"清空文件夹失败: {folder_path}")
262
+ print(f"Failed to clear the folder: {folder_path}")
259
263
  print(e)
260
264
 
261
265
 
@@ -264,7 +268,7 @@ def remove_empty_folder(path, print_info=1):
264
268
  """
265
269
  # 描述:清理空文件夹
266
270
  # 使用示例
267
- remove_empty_folder(r'E:\Data\2024\09\17', print_info=1)
271
+ remove_empty_folder(r'E:\\Data\\2024\\09\\17', print_info=1)
268
272
  param {*} path # 文件夹路径
269
273
  param {*} print_info # 是否打印信息
270
274
  """
@@ -299,13 +303,13 @@ def remove(pattern):
299
303
  Parameters:
300
304
  pattern : str
301
305
  File path or string containing wildcards. For example:
302
- - r'E:\Code\Python\Model\WRF\Radar2\bzip2-radar-0*'
306
+ - r'E:\\Code\\Python\\Model\\WRF\\Radar2\\bzip2-radar-0*'
303
307
  - 'bzip2-radar-0*' (assuming you are already in the target directory)
304
308
 
305
309
  Usage examples:
306
- remove(r'E:\Code\Python\Model\WRF\Radar2\bzip2-radar-0*')
310
+ remove(r'E:\\Code\\Python\\Model\\WRF\\Radar2\\bzip2-radar-0*')
307
311
  or
308
- os.chdir(r'E:\Code\Python\Model\WRF\Radar2')
312
+ os.chdir(r'E:\\Code\\Python\\Model\\WRF\\Radar2')
309
313
  remove('bzip2-radar-0*')
310
314
 
311
315
  last updated: 2025-01-10 11:49:13
@@ -406,4 +410,4 @@ if __name__ == "__main__":
406
410
  # print(newpath)
407
411
  pass
408
412
 
409
- remove(r"I:\Delete\test\*")
413
+ remove(r"I:\\Delete\\test\\*")
@@ -20,7 +20,9 @@ import numpy as np
20
20
  import xarray as xr
21
21
  from rich import print
22
22
 
23
- __all__ = ["get_var", "extract", "save", "merge", "modify", "rename", "check", "convert_longitude", "isel"]
23
+ from oafuncs._script.plot_dataset import func_plot_dataset
24
+
25
+ __all__ = ["get_var", "extract", "save", "merge", "modify", "rename", "check", "convert_longitude", "isel", "draw"]
24
26
 
25
27
 
26
28
  def get_var(file, *vars):
@@ -187,13 +189,13 @@ def merge(file_list, var_name=None, dim_name=None, target_filename=None):
187
189
  Merge variables from multiple NetCDF files along a specified dimension and write to a new file.
188
190
  If var_name is a string, it is considered a single variable; if it is a list and has only one element, it is also a single variable;
189
191
  If the list has more than one element, it is a multi-variable; if var_name is None, all variables are merged.
190
-
192
+
191
193
  Parameters:
192
194
  file_list: List of NetCDF file paths
193
195
  var_name: Name of the variable to be extracted or a list of variable names, default is None, which means all variables are extracted
194
196
  dim_name: Dimension name used for merging
195
197
  target_filename: Target file name after merging
196
-
198
+
197
199
  Example:
198
200
  merge(file_list, var_name='u', dim_name='time', target_filename='merged.nc')
199
201
  merge(file_list, var_name=['u', 'v'], dim_name='time', target_filename='merged.nc')
@@ -204,10 +206,10 @@ def merge(file_list, var_name=None, dim_name=None, target_filename=None):
204
206
  target_filename = "merged.nc"
205
207
  if not os.path.exists(os.path.dirname(str(target_filename))):
206
208
  os.makedirs(os.path.dirname(str(target_filename)))
207
-
209
+
208
210
  if isinstance(file_list, str):
209
211
  file_list = [file_list]
210
-
212
+
211
213
  # 初始化变量名列表
212
214
  var_names = None
213
215
 
@@ -228,7 +230,7 @@ def merge(file_list, var_name=None, dim_name=None, target_filename=None):
228
230
  merged_data = {}
229
231
 
230
232
  # 遍历文件列表
231
- print('Reading file ...')
233
+ print("Reading file ...")
232
234
  for i, file in enumerate(file_list):
233
235
  # 更新track描述进度
234
236
  # print(f"\rReading file {i + 1}/{len(file_list)}...", end="")
@@ -264,15 +266,15 @@ def merge(file_list, var_name=None, dim_name=None, target_filename=None):
264
266
 
265
267
 
266
268
  def _modify_var(nc_file_path, variable_name, new_value):
267
- """
269
+ """
268
270
  Description:
269
271
  Modify the value of a variable in a NetCDF file using the netCDF4 library.
270
-
272
+
271
273
  Parameters:
272
274
  nc_file_path (str): The path to the NetCDF file.
273
275
  variable_name (str): The name of the variable to be modified.
274
276
  new_value (numpy.ndarray): The new value of the variable.
275
-
277
+
276
278
  Example:
277
279
  modify_var('test.nc', 'u', np.random.rand(100, 50))
278
280
  """
@@ -290,16 +292,16 @@ def _modify_var(nc_file_path, variable_name, new_value):
290
292
 
291
293
 
292
294
  def _modify_attr(nc_file_path, variable_name, attribute_name, attribute_value):
293
- """
295
+ """
294
296
  Description:
295
297
  Add or modify an attribute of a variable in a NetCDF file using the netCDF4 library.
296
-
298
+
297
299
  Parameters:
298
300
  nc_file_path (str): The path to the NetCDF file.
299
301
  variable_name (str): The name of the variable to be modified.
300
302
  attribute_name (str): The name of the attribute to be added or modified.
301
303
  attribute_value (any): The value of the attribute.
302
-
304
+
303
305
  Example:
304
306
  modify_attr('test.nc', 'temperature', 'long_name', 'Temperature in Celsius')
305
307
  """
@@ -321,17 +323,17 @@ def _modify_attr(nc_file_path, variable_name, attribute_name, attribute_value):
321
323
  raise RuntimeError(f"An error occurred: {e}")
322
324
 
323
325
 
324
- def modify(nc_file,var_name,attr_name=None,new_value=None):
326
+ def modify(nc_file, var_name, attr_name=None, new_value=None):
325
327
  """
326
328
  Description:
327
329
  Modify the value of a variable or the value of an attribute in a NetCDF file.
328
-
330
+
329
331
  Parameters:
330
332
  nc_file (str): The path to the NetCDF file.
331
333
  var_name (str): The name of the variable to be modified.
332
334
  attr_name (str): The name of the attribute to be modified. If None, the variable value will be modified.
333
335
  new_value (any): The new value of the variable or attribute.
334
-
336
+
335
337
  Example:
336
338
  modify('test.nc', 'temperature', 'long_name', 'Temperature in Celsius')
337
339
  modify('test.nc', 'temperature', None, np.random.rand(100, 50))
@@ -387,7 +389,9 @@ def check(ncfile: str, delete_switch: bool = False) -> bool:
387
389
  is_valid = False
388
390
 
389
391
  if not os.path.exists(ncfile):
390
- print(f"File missing: {ncfile}")
392
+ print(f"[#ffeac5]Local file missing: [#009d88]{ncfile}")
393
+ # 提示:提示文件缺失也许是正常的,这只是检查文件是否存在于本地
394
+ print("[#d6d9fd]Note: File missing may be normal, this is just to check if the file exists locally.")
391
395
  return False
392
396
 
393
397
  try:
@@ -433,12 +437,12 @@ def convert_longitude(ds, lon_name="longitude", convert=180):
433
437
  """
434
438
  Description:
435
439
  Convert the longitude array to a specified range.
436
-
440
+
437
441
  Parameters:
438
442
  ds (xarray.Dataset): The xarray dataset containing the longitude data.
439
443
  lon_name (str): The name of the longitude variable, default is "longitude".
440
444
  convert (int): The target range to convert to, can be 180 or 360, default is 180.
441
-
445
+
442
446
  Returns:
443
447
  xarray.Dataset: The xarray dataset with the converted longitude.
444
448
  """
@@ -477,6 +481,43 @@ def isel(ncfile, dim_name, slice_list):
477
481
  return ds_new
478
482
 
479
483
 
484
+ def draw(output_dir=None, dataset=None, ncfile=None, xyzt_dims=("longitude", "latitude", "level", "time"), plot_type="contourf",fixed_colorscale=False):
485
+ """
486
+ Description:
487
+ Draw the data in the netCDF file
488
+
489
+ Parameters:
490
+ ncfile: str, the path of the netCDF file
491
+ output_dir: str, the path of the output directory
492
+ x_dim: str, the name of the x dimension
493
+ y_dim: str, the name of the y dimension
494
+ z_dim: str, the name of the z dimension
495
+ t_dim: str, the name of the t dimension
496
+ plot_type: str, the type of the plot, default is "contourf" (contourf, contour)
497
+ fixed_colorscale: bool, whether to use fixed colorscale, default is False
498
+
499
+ Example:
500
+ draw(ncfile, output_dir, x_dim="longitude", y_dim="latitude", z_dim="level", t_dim="time", fixed_colorscale=False)
501
+ """
502
+ if output_dir is None:
503
+ output_dir = str(os.getcwd())
504
+ if isinstance(xyzt_dims, (list, tuple)):
505
+ xyzt_dims = tuple(xyzt_dims)
506
+ else:
507
+ raise ValueError("xyzt_dims must be a list or tuple")
508
+ if dataset is not None:
509
+ func_plot_dataset(dataset, output_dir, xyzt_dims, plot_type, fixed_colorscale)
510
+ else:
511
+ if ncfile is not None:
512
+ if check(ncfile):
513
+ ds = xr.open_dataset(ncfile)
514
+ func_plot_dataset(ds, output_dir, xyzt_dims, plot_type, fixed_colorscale)
515
+ else:
516
+ print(f"Invalid file: {ncfile}")
517
+ else:
518
+ print("No dataset or file provided.")
519
+
520
+
480
521
  if __name__ == "__main__":
481
522
  data = np.random.rand(100, 50)
482
523
  save(r"test.nc", data, "data", {"time": np.linspace(0, 120, 100), "lev": np.linspace(0, 120, 50)}, "a")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: oafuncs
3
- Version: 0.0.97
3
+ Version: 0.0.97.1
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -9,7 +9,6 @@ License: MIT
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
@@ -33,7 +32,6 @@ Requires-Dist: geopandas
33
32
  Requires-Dist: Cartopy
34
33
  Requires-Dist: rasterio
35
34
  Requires-Dist: salem
36
- Requires-Dist: calendar
37
35
  Dynamic: author
38
36
  Dynamic: author-email
39
37
  Dynamic: classifier
@@ -15,7 +15,10 @@ oafuncs.egg-info/SOURCES.txt
15
15
  oafuncs.egg-info/dependency_links.txt
16
16
  oafuncs.egg-info/requires.txt
17
17
  oafuncs.egg-info/top_level.txt
18
+ oafuncs/_script/__init__.py
19
+ oafuncs/_script/plot_dataset.py
18
20
  oafuncs/data_store/OAFuncs.png
21
+ oafuncs/data_store/hycom_3hourly.png
19
22
  oafuncs/oa_down/User_Agent-list.txt
20
23
  oafuncs/oa_down/__init__.py
21
24
  oafuncs/oa_down/hycom_3hourly.py
@@ -13,4 +13,3 @@ geopandas
13
13
  Cartopy
14
14
  rasterio
15
15
  salem
16
- calendar
@@ -12,13 +12,13 @@ from shutil import rmtree
12
12
  from setuptools import Command, find_packages, setup
13
13
 
14
14
  # Package meta-data.
15
- NAME = 'oafuncs'
16
- DESCRIPTION = 'Oceanic and Atmospheric Functions'
17
- URL = 'https://github.com/Industry-Pays/OAFuncs'
18
- EMAIL = 'liukun0312@stu.ouc.edu.cn'
19
- AUTHOR = 'Kun Liu'
20
- REQUIRES_PYTHON = '>=3.9.0' # 2025/01/05
21
- VERSION = '0.0.97'
15
+ NAME = "oafuncs"
16
+ DESCRIPTION = "Oceanic and Atmospheric Functions"
17
+ URL = "https://github.com/Industry-Pays/OAFuncs"
18
+ EMAIL = "liukun0312@stu.ouc.edu.cn"
19
+ AUTHOR = "Kun Liu"
20
+ REQUIRES_PYTHON = ">=3.9.0" # 2025/03/13
21
+ VERSION = "0.0.97.1"
22
22
 
23
23
  # What packages are required for this module to be executed?
24
24
  REQUIRED = [
@@ -45,9 +45,6 @@ REQUIRED = [
45
45
  # ------- Data ------
46
46
  "rasterio", # 裁剪数据
47
47
  "salem",
48
- # ------ Time ------
49
- "calendar",
50
- # ------ Other ------
51
48
  ]
52
49
 
53
50
  # What packages are optional?
@@ -65,8 +62,8 @@ here = os.path.abspath(os.path.dirname(__file__))
65
62
  # Import the README and use it as the long-description.
66
63
  # Note: this will only work if 'README.md' is present in your MANIFEST.in file!
67
64
  try:
68
- with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
69
- long_description = '\n' + f.read()
65
+ with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
66
+ long_description = "\n" + f.read()
70
67
  except FileNotFoundError:
71
68
  long_description = DESCRIPTION
72
69
 
@@ -74,22 +71,22 @@ except FileNotFoundError:
74
71
  about = {}
75
72
  if not VERSION:
76
73
  project_slug = NAME.lower().replace("-", "_").replace(" ", "_")
77
- with open(os.path.join(here, project_slug, '__version__.py')) as f:
74
+ with open(os.path.join(here, project_slug, "__version__.py")) as f:
78
75
  exec(f.read(), about)
79
76
  else:
80
- about['__version__'] = VERSION
77
+ about["__version__"] = VERSION
81
78
 
82
79
 
83
80
  class UploadCommand(Command):
84
81
  """Support setup.py upload."""
85
82
 
86
- description = 'Build and publish the package.'
83
+ description = "Build and publish the package."
87
84
  user_options = []
88
85
 
89
86
  @staticmethod
90
87
  def status(s):
91
88
  """Prints things in bold."""
92
- print('\033[1m{0}\033[0m'.format(s))
89
+ print("\033[1m{0}\033[0m".format(s))
93
90
 
94
91
  def initialize_options(self):
95
92
  pass
@@ -99,17 +96,16 @@ class UploadCommand(Command):
99
96
 
100
97
  def run(self):
101
98
  try:
102
- self.status('Removing previous builds…')
103
- rmtree(os.path.join(here, 'dist'))
99
+ self.status("Removing previous builds…")
100
+ rmtree(os.path.join(here, "dist"))
104
101
  except OSError:
105
102
  pass
106
103
 
107
- self.status('Building Source and Wheel (universal) distribution…')
108
- os.system(
109
- '{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
104
+ self.status("Building Source and Wheel (universal) distribution…")
105
+ os.system("{0} setup.py sdist bdist_wheel".format(sys.executable))
110
106
 
111
- self.status('Uploading the package to PyPI via Twine…')
112
- os.system('twine upload dist/*')
107
+ self.status("Uploading the package to PyPI via Twine…")
108
+ os.system("twine upload dist/*")
113
109
 
114
110
  # self.status('Pushing git tags…')
115
111
  # os.system('git tag v{0}'.format(about['__version__']))
@@ -118,6 +114,12 @@ class UploadCommand(Command):
118
114
  sys.exit()
119
115
 
120
116
 
117
+ # 确保包含所有包
118
+ packages = find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"])
119
+ # 显式添加 data_store 目录
120
+ if "oafuncs.data_store" not in packages:
121
+ packages.append("oafuncs.data_store")
122
+
121
123
  # Where the magic happens:
122
124
  setup(
123
125
  name=NAME,
@@ -129,24 +131,17 @@ setup(
129
131
  author_email=EMAIL,
130
132
  python_requires=REQUIRES_PYTHON,
131
133
  url=URL,
132
- packages=find_packages(exclude=["oa_*", "oa_down", "oa_sign", "oa_tool"]),
133
- # packages=find_packages(exclude=["nc", "file", "*.tests.*", "tests.*"]),
134
- # If your package is a single module, use this instead of 'packages':
135
- # py_modules=['mypackage'],
136
- # entry_points={
137
- # 'console_scripts': ['mycli=mymodule:cli'],
138
- # },
134
+ packages=packages, # 使用修改过的包列表
139
135
  install_requires=REQUIRED,
140
136
  extras_require=EXTRAS,
141
137
  include_package_data=True,
142
- license="MIT",
138
+ license="MIT", # 确保这里使用的是许可证名称而非文件名
143
139
  classifiers=[
144
140
  # Trove classifiers
145
141
  # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
146
142
  "License :: OSI Approved :: MIT License",
147
143
  "Programming Language :: Python",
148
144
  "Programming Language :: Python :: 3",
149
- "Programming Language :: Python :: 3.9",
150
145
  "Programming Language :: Python :: 3.10",
151
146
  "Programming Language :: Python :: 3.11",
152
147
  "Programming Language :: Python :: 3.12",
@@ -1,2 +0,0 @@
1
- include oafuncs/oa_down/User_Agent-list.txt
2
- include oafuncs/data_store/OAFuncs.png
File without changes
File without changes
File without changes
File without changes
File without changes