oafuncs 0.0.98.23__tar.gz → 0.0.98.25__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 (53) hide show
  1. {oafuncs-0.0.98.23/oafuncs.egg-info → oafuncs-0.0.98.25}/PKG-INFO +1 -1
  2. oafuncs-0.0.98.25/oafuncs/_script/data_interp_geo.py +173 -0
  3. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_data.py +14 -13
  4. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25/oafuncs.egg-info}/PKG-INFO +1 -1
  5. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/setup.py +1 -1
  6. oafuncs-0.0.98.23/oafuncs/_script/data_interp_geo.py +0 -235
  7. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/LICENSE.txt +0 -0
  8. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/MANIFEST.in +0 -0
  9. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/README.md +0 -0
  10. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/__init__.py +0 -0
  11. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_data/hycom.png +0 -0
  12. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_data/oafuncs.png +0 -0
  13. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/cprogressbar.py +0 -0
  14. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/data_interp.py +0 -0
  15. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/email.py +0 -0
  16. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/netcdf_merge.py +0 -0
  17. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/netcdf_modify.py +0 -0
  18. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/netcdf_write.py +0 -0
  19. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/parallel.py +0 -0
  20. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/parallel_test.py +0 -0
  21. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/plot_dataset.py +0 -0
  22. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/_script/replace_file_content.py +0 -0
  23. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_cmap.py +0 -0
  24. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_date.py +0 -0
  25. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/User_Agent-list.txt +0 -0
  26. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/__init__.py +0 -0
  27. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/hycom_3hourly.py +0 -0
  28. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/hycom_3hourly_proxy.py +0 -0
  29. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/idm.py +0 -0
  30. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/literature.py +0 -0
  31. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/read_proxy.py +0 -0
  32. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/test_ua.py +0 -0
  33. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_down/user_agent.py +0 -0
  34. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_draw.py +0 -0
  35. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_file.py +0 -0
  36. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_help.py +0 -0
  37. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_model/__init__.py +0 -0
  38. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_model/roms/__init__.py +0 -0
  39. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_model/roms/test.py +0 -0
  40. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_model/wrf/__init__.py +0 -0
  41. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_model/wrf/little_r.py +0 -0
  42. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_nc.py +0 -0
  43. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_python.py +0 -0
  44. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_sign/__init__.py +0 -0
  45. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_sign/meteorological.py +0 -0
  46. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_sign/ocean.py +0 -0
  47. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_sign/scientific.py +0 -0
  48. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs/oa_tool.py +0 -0
  49. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs.egg-info/SOURCES.txt +0 -0
  50. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs.egg-info/dependency_links.txt +0 -0
  51. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs.egg-info/requires.txt +0 -0
  52. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/oafuncs.egg-info/top_level.txt +0 -0
  53. {oafuncs-0.0.98.23 → oafuncs-0.0.98.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.23
3
+ Version: 0.0.98.25
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -0,0 +1,173 @@
1
+ import importlib.util
2
+ from typing import List, Union
3
+
4
+ import numpy as np
5
+
6
+ from oafuncs.oa_tool import PEx
7
+
8
+ # 检查pyinterp是否可用
9
+ pyinterp_available = importlib.util.find_spec("pyinterp") is not None
10
+
11
+ if pyinterp_available:
12
+ import pyinterp
13
+ import pyinterp.backends.xarray as pyxr
14
+ import xarray as xr
15
+
16
+
17
+ def _fill_nan_with_nearest(data: np.ndarray, source_lons: np.ndarray, source_lats: np.ndarray) -> np.ndarray:
18
+ """
19
+ 使用最近邻方法填充NaN值,适合地理数据。
20
+ """
21
+ if not np.isnan(data).any():
22
+ return data
23
+
24
+ # 创建掩码,区分有效值和NaN值
25
+ mask = ~np.isnan(data)
26
+ if not np.any(mask):
27
+ return data # 全是NaN,无法填充
28
+
29
+ # 使用pyinterp的RTree进行最近邻插值填充NaN
30
+ try:
31
+ if not pyinterp_available:
32
+ raise ImportError("pyinterp not available")
33
+
34
+ # 获取有效数据点的位置和值
35
+ valid_points = np.column_stack((source_lons[mask].ravel(), source_lats[mask].ravel()))
36
+ valid_values = data[mask].ravel()
37
+
38
+ # 创建RTree
39
+ tree = pyinterp.RTree()
40
+ tree.insert(valid_points.astype(np.float64), valid_values.astype(np.float64))
41
+
42
+ # 获取所有点的坐标
43
+ all_points = np.column_stack((source_lons.ravel(), source_lats.ravel()))
44
+
45
+ # 最近邻插值
46
+ filled_values = tree.query(all_points[:, 0], all_points[:, 1], k=1)
47
+
48
+ return filled_values.reshape(data.shape)
49
+
50
+ except Exception:
51
+ # 备选方案:使用scipy的最近邻
52
+ from scipy.interpolate import NearestNDInterpolator
53
+
54
+ points = np.column_stack((source_lons[mask].ravel(), source_lats[mask].ravel()))
55
+ values = data[mask].ravel()
56
+
57
+ if len(values) > 0:
58
+ interp = NearestNDInterpolator(points, values)
59
+ return interp(source_lons.ravel(), source_lats.ravel()).reshape(data.shape)
60
+ else:
61
+ return data # 无有效值可用于填充
62
+
63
+
64
+ def _interp_single_worker(*args):
65
+ """
66
+ 单slice插值worker,只使用pyinterp的bicubic方法,失败直接报错。
67
+ 参数: data_slice, source_lons, source_lats, target_lons, target_lats
68
+ """
69
+ if not pyinterp_available:
70
+ raise ImportError("pyinterp package is required for geographic interpolation")
71
+
72
+ data_slice, source_lons, source_lats, target_lons, target_lats = args
73
+
74
+ # 预处理:填充NaN值以确保数据完整
75
+ if np.isnan(data_slice).any():
76
+ data_filled = _fill_nan_with_nearest(data_slice, source_lons, source_lats)
77
+ else:
78
+ data_filled = data_slice
79
+
80
+ # 创建xarray DataArray
81
+
82
+ da = xr.DataArray(
83
+ data_filled,
84
+ coords={"lat": source_lats, "lon": source_lons},
85
+ dims=("lat", "lon"),
86
+ )
87
+
88
+ # 创建Grid2D对象
89
+ grid = pyxr.Grid2D(da)
90
+
91
+ # 使用bicubic方法插值
92
+ result = grid.bicubic(coords={"lon": target_lons.flatten(), "lat": target_lats.flatten()}, bounds_error=False, num_threads=1).reshape(target_lons.shape)
93
+
94
+ return result
95
+
96
+
97
+ def interp_2d_func_geo(
98
+ target_x_coordinates: Union[np.ndarray, List[float]],
99
+ target_y_coordinates: Union[np.ndarray, List[float]],
100
+ source_x_coordinates: Union[np.ndarray, List[float]],
101
+ source_y_coordinates: Union[np.ndarray, List[float]],
102
+ source_data: np.ndarray,
103
+ ) -> np.ndarray:
104
+ """
105
+ 使用pyinterp进行地理插值,只使用bicubic方法。
106
+
107
+ Args:
108
+ target_x_coordinates: 目标点经度 (-180 to 180 或 0 to 360)
109
+ target_y_coordinates: 目标点纬度 (-90 to 90)
110
+ source_x_coordinates: 源数据经度 (-180 to 180 或 0 to 360)
111
+ source_y_coordinates: 源数据纬度 (-90 to 90)
112
+ source_data: 多维数组,最后两个维度为空间维度
113
+
114
+ Returns:
115
+ np.ndarray: 插值后的数据数组
116
+ """
117
+ if not pyinterp_available:
118
+ raise ImportError("pyinterp package is required for geographic interpolation")
119
+
120
+ # 验证纬度范围
121
+ if np.nanmin(target_y_coordinates) < -90 or np.nanmax(target_y_coordinates) > 90:
122
+ raise ValueError("Target latitude must be in range [-90, 90].")
123
+ if np.nanmin(source_y_coordinates) < -90 or np.nanmax(source_y_coordinates) > 90:
124
+ raise ValueError("Source latitude must be in range [-90, 90].")
125
+
126
+ # 确保使用numpy数组
127
+ source_x_coordinates = np.array(source_x_coordinates)
128
+ source_y_coordinates = np.array(source_y_coordinates)
129
+ target_x_coordinates = np.array(target_x_coordinates)
130
+ target_y_coordinates = np.array(target_y_coordinates)
131
+
132
+ # 创建网格坐标(如果是一维的)
133
+ if source_x_coordinates.ndim == 1:
134
+ source_x_coordinates, source_y_coordinates = np.meshgrid(source_x_coordinates, source_y_coordinates)
135
+ if target_x_coordinates.ndim == 1:
136
+ target_x_coordinates, target_y_coordinates = np.meshgrid(target_x_coordinates, target_y_coordinates)
137
+
138
+ # 验证源数据形状
139
+ if source_x_coordinates.shape != source_data.shape[-2:] or source_y_coordinates.shape != source_data.shape[-2:]:
140
+ raise ValueError("Shape of source_data does not match shape of source_x_coordinates or source_y_coordinates.")
141
+
142
+ # 处理多维数据
143
+ data_dims = source_data.ndim
144
+ if data_dims < 2:
145
+ raise ValueError(f"Source data must have at least 2 dimensions, but got {data_dims}.")
146
+ elif data_dims > 4:
147
+ raise ValueError(f"Source data has {data_dims} dimensions, but this function currently supports up to 4.")
148
+
149
+ # 扩展到4D
150
+ num_dims_to_add = 4 - data_dims
151
+ source_data = source_data.reshape((1,) * num_dims_to_add + source_data.shape)
152
+ t, z, y, x = source_data.shape
153
+
154
+ # 准备并行处理参数
155
+ params = []
156
+ for t_index in range(t):
157
+ for z_index in range(z):
158
+ params.append(
159
+ (
160
+ source_data[t_index, z_index],
161
+ source_x_coordinates[0, :], # 假设经度在每行都相同
162
+ source_y_coordinates[:, 0], # 假设纬度在每列都相同
163
+ target_x_coordinates,
164
+ target_y_coordinates,
165
+ )
166
+ )
167
+
168
+ # 并行执行插值
169
+ with PEx() as executor:
170
+ results = executor.run(_interp_single_worker, params)
171
+
172
+ # 还原到原始维度
173
+ return np.squeeze(np.array(results).reshape((t, z) + target_x_coordinates.shape))
@@ -13,7 +13,6 @@ SystemInfo: Windows 11
13
13
  Python Version: 3.11
14
14
  """
15
15
 
16
-
17
16
  from typing import Any, List, Union
18
17
 
19
18
  import numpy as np
@@ -22,7 +21,6 @@ import xarray as xr
22
21
  from rich import print
23
22
  from scipy.interpolate import interp1d
24
23
 
25
-
26
24
  __all__ = ["interp_along_dim", "interp_2d", "interp_2d_geo", "ensure_list", "mask_shapefile"]
27
25
 
28
26
 
@@ -152,7 +150,7 @@ def interp_2d(
152
150
  >>> print(result.shape) # Expected output: (3, 3)
153
151
  """
154
152
  from ._script.data_interp import interp_2d_func
155
-
153
+
156
154
  return interp_2d_func(
157
155
  target_x_coordinates=target_x_coordinates,
158
156
  target_y_coordinates=target_y_coordinates,
@@ -162,7 +160,14 @@ def interp_2d(
162
160
  interpolation_method=interpolation_method,
163
161
  )
164
162
 
165
- def interp_2d_geo(target_x_coordinates: Union[np.ndarray, List[float]], target_y_coordinates: Union[np.ndarray, List[float]], source_x_coordinates: Union[np.ndarray, List[float]], source_y_coordinates: Union[np.ndarray, List[float]], source_data: np.ndarray, interpolation_method: str = "cubic") -> np.ndarray:
163
+
164
+ def interp_2d_geo(
165
+ target_x_coordinates: Union[np.ndarray, List[float]],
166
+ target_y_coordinates: Union[np.ndarray, List[float]],
167
+ source_x_coordinates: Union[np.ndarray, List[float]],
168
+ source_y_coordinates: Union[np.ndarray, List[float]],
169
+ source_data: np.ndarray,
170
+ ) -> np.ndarray:
166
171
  """
167
172
  使用pyinterp进行地理插值,适用于全球尺度的地理数据与区域数据。
168
173
 
@@ -178,11 +183,7 @@ def interp_2d_geo(target_x_coordinates: Union[np.ndarray, List[float]], target_y
178
183
  source_x_coordinates: 源数据经度 (-180 to 180 或 0 to 360)
179
184
  source_y_coordinates: 源数据纬度 (-90 to 90)
180
185
  source_data: 多维数组,最后两个维度为空间维度
181
- interpolation_method: 插值方法:
182
- - 'nearest': 最近邻插值
183
- - 'linear'/'bilinear': 双线性插值
184
- - 'cubic': 三次样条插值
185
- - 'quintic': 五次样条插值
186
+ interpolation_method: 插值方法: 只会使用 'bicubic' 方法。
186
187
 
187
188
  Returns:
188
189
  np.ndarray: 插值后的数据数组
@@ -198,19 +199,19 @@ def interp_2d_geo(target_x_coordinates: Union[np.ndarray, List[float]], target_y
198
199
  """
199
200
  # 使用importlib检查pyinterp是否可用,避免直接import导致的警告
200
201
  import importlib.util
202
+
201
203
  pyinterp_available = importlib.util.find_spec("pyinterp") is not None
202
-
204
+
203
205
  if pyinterp_available:
204
206
  # 只在pyinterp可用时才导入相关模块
205
207
  from ._script.data_interp_geo import interp_2d_func_geo
206
-
208
+
207
209
  return interp_2d_func_geo(
208
210
  target_x_coordinates=target_x_coordinates,
209
211
  target_y_coordinates=target_y_coordinates,
210
212
  source_x_coordinates=source_x_coordinates,
211
213
  source_y_coordinates=source_y_coordinates,
212
214
  source_data=source_data,
213
- interpolation_method=interpolation_method,
214
215
  )
215
216
  else:
216
217
  print("[yellow]警告: pyinterp模块未安装,无法使用球面坐标插值。尝试使用平面插值作为备选方案。[/yellow]")
@@ -222,11 +223,11 @@ def interp_2d_geo(target_x_coordinates: Union[np.ndarray, List[float]], target_y
222
223
  source_x_coordinates=source_x_coordinates,
223
224
  source_y_coordinates=source_y_coordinates,
224
225
  source_data=source_data,
225
- interpolation_method=interpolation_method,
226
226
  )
227
227
  except Exception as e:
228
228
  raise ImportError(f"pyinterp不可用且备选插值方法也失败: {e}")
229
229
 
230
+
230
231
  def mask_shapefile(
231
232
  data_array: np.ndarray,
232
233
  longitudes: np.ndarray,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.23
3
+ Version: 0.0.98.25
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -18,7 +18,7 @@ URL = "https://github.com/Industry-Pays/OAFuncs"
18
18
  EMAIL = "liukun0312@stu.ouc.edu.cn"
19
19
  AUTHOR = "Kun Liu"
20
20
  REQUIRES_PYTHON = ">=3.9.0" # 2025/03/13
21
- VERSION = "0.0.98.23"
21
+ VERSION = "0.0.98.25"
22
22
 
23
23
  # What packages are required for this module to be executed?
24
24
  REQUIRED = [
@@ -1,235 +0,0 @@
1
- from typing import List, Union
2
-
3
- import numpy as np
4
- import importlib.util
5
-
6
- from oafuncs.oa_tool import PEx
7
-
8
- # 检查pyinterp是否可用
9
- pyinterp_available = importlib.util.find_spec("pyinterp") is not None
10
-
11
- # 仅在pyinterp可用时导入相关模块
12
- if pyinterp_available:
13
- import pyinterp
14
- from pyinterp.interpolator import RegularGridInterpolator, RTree
15
-
16
-
17
- def _interp_single_worker(*args):
18
- """
19
- 用于PEx并行的单slice插值worker。
20
- 参数: data_slice, origin_points, target_points, interpolation_method, target_shape, source_xy_shape
21
- 使用pyinterp进行地理插值
22
- """
23
- # 确保pyinterp可用
24
- if not pyinterp_available:
25
- raise ImportError("pyinterp package is required for geographic interpolation")
26
-
27
- data_slice, origin_points, target_points, interpolation_method, target_shape, source_xy_shape = args
28
-
29
- # 处理无效数据点
30
- valid_mask = ~np.isnan(data_slice.ravel())
31
- if np.count_nonzero(valid_mask) < 10:
32
- return np.full(target_shape, np.nanmean(data_slice))
33
-
34
- # 准备有效数据点
35
- valid_data = data_slice.ravel()[valid_mask]
36
- valid_points = origin_points[valid_mask]
37
-
38
- # 根据插值方法选择合适的策略
39
- if origin_points.shape[0] == source_xy_shape[0] * source_xy_shape[1]: # 规则网格
40
- try:
41
- # 尝试使用规则网格插值
42
- y_size, x_size = source_xy_shape
43
- lons = origin_points[:, 0].reshape(y_size, x_size)[0, :]
44
- lats = origin_points[:, 1].reshape(y_size, x_size)[:, 0]
45
-
46
- # 检查网格数据的有效性
47
- grid_data = data_slice.reshape(source_xy_shape)
48
- nan_ratio = np.isnan(grid_data).sum() / grid_data.size
49
- if nan_ratio > 0.5: # 如果超过50%是NaN,跳过规则网格插值
50
- raise ValueError("Too many NaN values in grid data")
51
-
52
- # 创建pyinterp网格 - 设置经度循环
53
- is_global = np.abs((lons[-1] - lons[0]) % 360 - 360) < 1e-6
54
- grid = pyinterp.Grid2D(
55
- x=pyinterp.Axis(lons, is_circle=is_global), # 根据数据判断是否为全球网格
56
- y=pyinterp.Axis(lats),
57
- array=grid_data,
58
- increasing_axes=(-2, -1), # 确保坐标轴方向正确
59
- )
60
-
61
- # 创建插值器并执行插值
62
- method_map = {"bilinear": "bilinear", "linear": "bilinear", "cubic": "bicubic", "nearest": "nearest"}
63
- interpolator = RegularGridInterpolator(grid, method=method_map.get(interpolation_method, "bilinear"))
64
-
65
- # 执行插值 - 使用geodetic坐标系统确保正确处理地球曲率
66
- coords = pyinterp.geodetic.Coordinates(target_points[:, 0], target_points[:, 1], pyinterp.geodetic.System.WGS84)
67
-
68
- result = interpolator.interpolate(coords).reshape(target_shape)
69
-
70
- # 如果规则网格插值没有产生太多NaN值,直接返回结果
71
- if np.isnan(result).sum() / result.size < 0.05:
72
- return result
73
-
74
- except Exception: # noqa
75
- # 失败时使用RTree插值
76
- pass
77
-
78
- # 使用RTree进行非规则网格插值或填补规则网格产生的NaN
79
- try:
80
- # 创建RTree插值器
81
- mesh = RTree(pyinterp.geodetic.Coordinates(valid_points[:, 0], valid_points[:, 1], pyinterp.geodetic.System.WGS84), valid_data)
82
-
83
- # 根据插值方法和有效点数量选择合适的插值策略
84
- coords = pyinterp.geodetic.Coordinates(target_points[:, 0], target_points[:, 1], pyinterp.geodetic.System.WGS84)
85
-
86
- if interpolation_method in ["cubic", "quintic"] and len(valid_data) > 100:
87
- # 对于点数充足的情况,高阶插值使用径向基函数
88
- result = mesh.radial_basis_function(
89
- coords,
90
- function="thin_plate", # 薄板样条,适合地理数据
91
- epsilon=0.1, # 平滑参数
92
- norm="geodetic", # 使用地理距离
93
- within=False, # 允许外推
94
- ).reshape(target_shape)
95
- else:
96
- # 使用IDW,动态调整k值
97
- k_value = max(min(int(np.sqrt(len(valid_data))), 16), 4) # 自适应近邻点数
98
- result, _ = mesh.inverse_distance_weighting(
99
- coords,
100
- k=k_value,
101
- p=2.0, # 平方反比权重
102
- within=False, # 允许外推
103
- ).reshape(target_shape)
104
-
105
- # 检查插值结果,如果有NaN,尝试使用最近邻补充
106
- if np.isnan(result).any():
107
- nan_mask = np.isnan(result)
108
- nan_coords = pyinterp.geodetic.Coordinates(target_points[nan_mask.ravel(), 0], target_points[nan_mask.ravel(), 1], pyinterp.geodetic.System.WGS84)
109
- nn_values, _ = mesh.k_nearest(nan_coords, k=1)
110
- result[nan_mask] = nn_values
111
-
112
- except Exception:
113
- # 如果所有复杂插值方法都失败,使用最基本的最近邻
114
- try:
115
- # 创建新的RTree对象尝试避免之前可能的问题
116
- simple_mesh = RTree(pyinterp.geodetic.Coordinates(valid_points[:, 0], valid_points[:, 1], pyinterp.geodetic.System.WGS84), valid_data)
117
-
118
- simple_coords = pyinterp.geodetic.Coordinates(target_points[:, 0], target_points[:, 1], pyinterp.geodetic.System.WGS84)
119
-
120
- result, _ = simple_mesh.k_nearest(simple_coords, k=1).reshape(target_shape)
121
- except Exception:
122
- # 极端情况下,使用平均值填充
123
- result = np.full(target_shape, np.nanmean(valid_data))
124
-
125
- return result
126
-
127
-
128
- def interp_2d_func_geo(target_x_coordinates: Union[np.ndarray, List[float]], target_y_coordinates: Union[np.ndarray, List[float]], source_x_coordinates: Union[np.ndarray, List[float]], source_y_coordinates: Union[np.ndarray, List[float]], source_data: np.ndarray, interpolation_method: str = "cubic") -> np.ndarray:
129
- """
130
- 使用pyinterp进行地理插值,适用于全球尺度的地理数据与区域数据。
131
-
132
- 特点:
133
- - 正确处理经度跨越日期线的情况
134
- - 自动选择最佳插值策略
135
- - 处理规则网格和非规则数据
136
- - 支持多维数据并行处理
137
-
138
- Args:
139
- target_x_coordinates: 目标点经度 (-180 to 180 或 0 to 360)
140
- target_y_coordinates: 目标点纬度 (-90 to 90)
141
- source_x_coordinates: 源数据经度 (-180 to 180 或 0 to 360)
142
- source_y_coordinates: 源数据纬度 (-90 to 90)
143
- source_data: 多维数组,最后两个维度为空间维度
144
- interpolation_method: 插值方法:
145
- - 'nearest': 最近邻插值
146
- - 'linear'/'bilinear': 双线性插值
147
- - 'cubic': 三次样条插值
148
- - 'quintic': 五次样条插值
149
-
150
- Returns:
151
- np.ndarray: 插值后的数据数组
152
-
153
- Examples:
154
- >>> # 全球数据插值示例
155
- >>> target_lon = np.arange(-180, 181, 1)
156
- >>> target_lat = np.arange(-90, 91, 1)
157
- >>> source_lon = np.arange(-180, 181, 5)
158
- >>> source_lat = np.arange(-90, 91, 5)
159
- >>> source_data = np.cos(np.deg2rad(source_lat.reshape(-1, 1))) * np.cos(np.deg2rad(source_lon))
160
- >>> result = interp_2d_func_geo(target_lon, target_lat, source_lon, source_lat, source_data)
161
- """
162
- # 确保pyinterp可用
163
- if not pyinterp_available:
164
- raise ImportError("pyinterp package is required for geographic interpolation")
165
-
166
- # 验证输入数据范围
167
- if np.nanmin(target_y_coordinates) < -90 or np.nanmax(target_y_coordinates) > 90:
168
- raise ValueError("[red]Target latitude must be in range [-90, 90].[/red]")
169
- if np.nanmin(source_y_coordinates) < -90 or np.nanmax(source_y_coordinates) > 90:
170
- raise ValueError("[red]Source latitude must be in range [-90, 90].[/red]")
171
-
172
- # 转换为网格坐标
173
- if len(target_y_coordinates.shape) == 1:
174
- target_x_coordinates, target_y_coordinates = np.meshgrid(target_x_coordinates, target_y_coordinates)
175
- if len(source_y_coordinates.shape) == 1:
176
- source_x_coordinates, source_y_coordinates = np.meshgrid(source_x_coordinates, source_y_coordinates)
177
-
178
- # 验证源数据形状
179
- if source_x_coordinates.shape != source_data.shape[-2:] or source_y_coordinates.shape != source_data.shape[-2:]:
180
- raise ValueError("[red]Shape of source_data does not match shape of source_x_coordinates or source_y_coordinates.[/red]")
181
-
182
- # 准备坐标点并统一经度表示系统
183
- target_points = np.column_stack((np.array(target_x_coordinates).ravel(), np.array(target_y_coordinates).ravel()))
184
- origin_points = np.column_stack((np.array(source_x_coordinates).ravel(), np.array(source_y_coordinates).ravel()))
185
- source_xy_shape = source_x_coordinates.shape
186
-
187
- # 统一经度表示系统
188
- origin_points = origin_points.copy()
189
- target_points = target_points.copy()
190
-
191
- # 检测经度系统并统一
192
- src_lon_range = np.nanmax(origin_points[:, 0]) - np.nanmin(origin_points[:, 0])
193
- tgt_lon_range = np.nanmax(target_points[:, 0]) - np.nanmin(target_points[:, 0])
194
-
195
- # 如果数据接近全球范围并且表示系统不同,则统一表示系统
196
- if (src_lon_range > 300 or tgt_lon_range > 300) and ((np.nanmax(target_points[:, 0]) > 180 and np.nanmin(origin_points[:, 0]) < 0) or (np.nanmax(origin_points[:, 0]) > 180 and np.nanmin(target_points[:, 0]) < 0)):
197
- # 优先使用[0,360]系统,因为它不会在日期线处断开
198
- if np.nanmax(target_points[:, 0]) > 180 or np.nanmax(origin_points[:, 0]) > 180:
199
- # 转换为[0,360]系统
200
- if np.nanmin(origin_points[:, 0]) < 0:
201
- origin_points[:, 0] = np.where(origin_points[:, 0] < 0, origin_points[:, 0] + 360, origin_points[:, 0])
202
- if np.nanmin(target_points[:, 0]) < 0:
203
- target_points[:, 0] = np.where(target_points[:, 0] < 0, target_points[:, 0] + 360, target_points[:, 0])
204
- else:
205
- # 转换为[-180,180]系统
206
- if np.nanmax(origin_points[:, 0]) > 180:
207
- origin_points[:, 0] = np.where(origin_points[:, 0] > 180, origin_points[:, 0] - 360, origin_points[:, 0])
208
- if np.nanmax(target_points[:, 0]) > 180:
209
- target_points[:, 0] = np.where(target_points[:, 0] > 180, target_points[:, 0] - 360, target_points[:, 0])
210
-
211
- # 处理多维数据
212
- data_dims = len(source_data.shape)
213
- if data_dims < 2:
214
- raise ValueError(f"[red]Source data must have at least 2 dimensions, but got {data_dims}.[/red]")
215
- elif data_dims > 4:
216
- raise ValueError(f"Source data has {data_dims} dimensions, but this function currently supports only up to 4.")
217
-
218
- num_dims_to_add = 4 - data_dims
219
- new_shape = (1,) * num_dims_to_add + source_data.shape
220
- new_src_data = source_data.reshape(new_shape)
221
-
222
- t, z, y, x = new_src_data.shape
223
-
224
- # 准备并行处理参数
225
- params = []
226
- target_shape = target_y_coordinates.shape
227
- for t_index in range(t):
228
- for z_index in range(z):
229
- params.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape, source_xy_shape))
230
-
231
- # 并行处理
232
- with PEx() as excutor:
233
- result = excutor.run(_interp_single_worker, params)
234
-
235
- return np.squeeze(np.array(result).reshape(t, z, *target_shape))
File without changes
File without changes
File without changes
File without changes