oafuncs 0.0.98.6__py3-none-any.whl → 0.0.98.8__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.
- oafuncs/oa_data.py +36 -27
- oafuncs/oa_down/hycom_3hourly.py +23 -12
- oafuncs/oa_draw.py +65 -0
- {oafuncs-0.0.98.6.dist-info → oafuncs-0.0.98.8.dist-info}/METADATA +2 -1
- {oafuncs-0.0.98.6.dist-info → oafuncs-0.0.98.8.dist-info}/RECORD +8 -8
- {oafuncs-0.0.98.6.dist-info → oafuncs-0.0.98.8.dist-info}/WHEEL +1 -1
- {oafuncs-0.0.98.6.dist-info → oafuncs-0.0.98.8.dist-info}/licenses/LICENSE.txt +0 -0
- {oafuncs-0.0.98.6.dist-info → oafuncs-0.0.98.8.dist-info}/top_level.txt +0 -0
oafuncs/oa_data.py
CHANGED
@@ -13,9 +13,7 @@ SystemInfo: Windows 11
|
|
13
13
|
Python Version: 3.11
|
14
14
|
"""
|
15
15
|
|
16
|
-
|
17
|
-
import multiprocessing as mp
|
18
|
-
from concurrent.futures import ThreadPoolExecutor
|
16
|
+
|
19
17
|
from typing import Any, List, Union
|
20
18
|
|
21
19
|
import numpy as np
|
@@ -24,6 +22,8 @@ import xarray as xr
|
|
24
22
|
from rich import print
|
25
23
|
from scipy.interpolate import griddata, interp1d
|
26
24
|
|
25
|
+
from oafuncs.oa_tool import PEx
|
26
|
+
|
27
27
|
__all__ = ["interp_along_dim", "interp_2d", "ensure_list", "mask_shapefile"]
|
28
28
|
|
29
29
|
|
@@ -115,6 +115,15 @@ def interp_along_dim(
|
|
115
115
|
return np.apply_along_axis(apply_interp_extrap, interpolation_axis, source_data)
|
116
116
|
|
117
117
|
|
118
|
+
def _interp_single_worker(*args):
|
119
|
+
"""
|
120
|
+
用于PEx并行的单slice插值worker,参数为(t, z, source_data, origin_points, target_points, interpolation_method, target_shape)
|
121
|
+
"""
|
122
|
+
data_slice, origin_points, target_points, interpolation_method, target_shape = args
|
123
|
+
|
124
|
+
return griddata(origin_points, data_slice.ravel(), target_points, method=interpolation_method).reshape(target_shape)
|
125
|
+
|
126
|
+
|
118
127
|
def interp_2d(
|
119
128
|
target_x_coordinates: Union[np.ndarray, List[float]],
|
120
129
|
target_y_coordinates: Union[np.ndarray, List[float]],
|
@@ -122,7 +131,6 @@ def interp_2d(
|
|
122
131
|
source_y_coordinates: Union[np.ndarray, List[float]],
|
123
132
|
source_data: np.ndarray,
|
124
133
|
interpolation_method: str = "linear",
|
125
|
-
use_parallel: bool = True,
|
126
134
|
) -> np.ndarray:
|
127
135
|
"""
|
128
136
|
Perform 2D interpolation on the last two dimensions of a multi-dimensional array.
|
@@ -151,10 +159,6 @@ def interp_2d(
|
|
151
159
|
>>> result = interp_2d(target_x_coordinates, target_y_coordinates, source_x_coordinates, source_y_coordinates, source_data)
|
152
160
|
>>> print(result.shape) # Expected output: (3, 3)
|
153
161
|
"""
|
154
|
-
|
155
|
-
def interp_single(data_slice: np.ndarray, target_points: np.ndarray, origin_points: np.ndarray, method: str) -> np.ndarray:
|
156
|
-
return griddata(origin_points, data_slice.ravel(), target_points, method=method).reshape(target_y_coordinates.shape)
|
157
|
-
|
158
162
|
if len(target_y_coordinates.shape) == 1:
|
159
163
|
target_x_coordinates, target_y_coordinates = np.meshgrid(target_x_coordinates, target_y_coordinates)
|
160
164
|
if len(source_y_coordinates.shape) == 1:
|
@@ -166,25 +170,30 @@ def interp_2d(
|
|
166
170
|
target_points = np.column_stack((np.array(target_y_coordinates).ravel(), np.array(target_x_coordinates).ravel()))
|
167
171
|
origin_points = np.column_stack((np.array(source_y_coordinates).ravel(), np.array(source_x_coordinates).ravel()))
|
168
172
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
173
|
+
data_dims = len(source_data.shape)
|
174
|
+
# Ensure source_data is 4D for consistent processing (t, z, y, x)
|
175
|
+
if data_dims < 2:
|
176
|
+
raise ValueError(f"[red]Source data must have at least 2 dimensions, but got {data_dims}.[/red]")
|
177
|
+
elif data_dims > 4:
|
178
|
+
# Or handle cases with more than 4 dimensions if necessary
|
179
|
+
raise ValueError(f"[red]Source data has {data_dims} dimensions, but this function currently supports only up to 4.[/red]")
|
180
|
+
|
181
|
+
# Reshape to 4D by adding leading dimensions of size 1 if needed
|
182
|
+
num_dims_to_add = 4 - data_dims
|
183
|
+
new_shape = (1,) * num_dims_to_add + source_data.shape
|
184
|
+
new_src_data = source_data.reshape(new_shape)
|
185
|
+
|
186
|
+
t, z, _, _ = new_src_data.shape
|
187
|
+
|
188
|
+
paras = []
|
189
|
+
target_shape = target_y_coordinates.shape
|
190
|
+
for t_index in range(t):
|
191
|
+
for z_index in range(z):
|
192
|
+
paras.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape))
|
193
|
+
excutor = PEx()
|
194
|
+
result = excutor.run(_interp_single_worker, paras)
|
195
|
+
|
196
|
+
return np.squeeze(np.array(result))
|
188
197
|
|
189
198
|
|
190
199
|
def mask_shapefile(
|
oafuncs/oa_down/hycom_3hourly.py
CHANGED
@@ -19,7 +19,6 @@ import logging
|
|
19
19
|
import os
|
20
20
|
import random
|
21
21
|
import re
|
22
|
-
import time
|
23
22
|
import warnings
|
24
23
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
25
24
|
from pathlib import Path
|
@@ -728,9 +727,15 @@ class _HycomDownloader:
|
|
728
727
|
|
729
728
|
elapsed = datetime.datetime.now() - start
|
730
729
|
# logging.info(f"File {file_name} downloaded, Time: {elapsed}")
|
731
|
-
logging.info(f"Saving {file_name}
|
730
|
+
logging.info(f"Saving {file_name} ...")
|
731
|
+
logging.info(f"Timing {elapsed} ...")
|
732
732
|
self.count["success"] += 1
|
733
733
|
count_dict["success"] += 1
|
734
|
+
# 输出一条绿色的成功消息
|
735
|
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
|
736
|
+
# print(f"{timestamp} - INFO - ", end="") # Output log prefix without newline
|
737
|
+
# print("[bold #3dfc40]Success")
|
738
|
+
print(f"{timestamp} - RESULT - [bold #3dfc40]Success")
|
734
739
|
return
|
735
740
|
|
736
741
|
except Exception as e:
|
@@ -744,6 +749,10 @@ class _HycomDownloader:
|
|
744
749
|
logging.error(f"Giving up on {file_name}")
|
745
750
|
self.count["fail"] += 1
|
746
751
|
count_dict["fail"] += 1
|
752
|
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
|
753
|
+
# print(f"{timestamp} - ERROR - ", end="")
|
754
|
+
# print("[bold red]Failed")
|
755
|
+
print(f"{timestamp} - RESULT - [bold red]Failure")
|
747
756
|
return
|
748
757
|
|
749
758
|
async def run(self):
|
@@ -757,8 +766,7 @@ class _HycomDownloader:
|
|
757
766
|
|
758
767
|
def _download_file(target_url, store_path, file_name, cover=False):
|
759
768
|
save_path = Path(store_path) / file_name
|
760
|
-
file_name_split = file_name.split("_")
|
761
|
-
file_name_split = file_name_split[:-1]
|
769
|
+
file_name_split = file_name.split("_")[:-1]
|
762
770
|
same_file = "_".join(file_name_split) + "*nc"
|
763
771
|
|
764
772
|
if match_time is not None:
|
@@ -775,10 +783,12 @@ def _download_file(target_url, store_path, file_name, cover=False):
|
|
775
783
|
print(f"[bold green]{file_name} is correct")
|
776
784
|
return
|
777
785
|
|
778
|
-
if not cover and os.path.exists(save_path):
|
779
|
-
|
780
|
-
|
781
|
-
|
786
|
+
# if not cover and os.path.exists(save_path):
|
787
|
+
# print(f"[bold #FFA54F]{save_path} exists, skipping ...")
|
788
|
+
# count_dict["skip"] += 1
|
789
|
+
# return
|
790
|
+
if cover and os.path.exists(save_path):
|
791
|
+
_clear_existing_file(save_path)
|
782
792
|
|
783
793
|
if same_file not in fsize_dict.keys():
|
784
794
|
check_nc(save_path, delete_if_invalid=True, print_messages=False)
|
@@ -786,6 +796,7 @@ def _download_file(target_url, store_path, file_name, cover=False):
|
|
786
796
|
get_mean_size = _get_mean_size_move(same_file, save_path)
|
787
797
|
|
788
798
|
if _check_existing_file(save_path, get_mean_size):
|
799
|
+
print(f"[bold #FFA54F]{save_path} exists, skipping ...")
|
789
800
|
count_dict["skip"] += 1
|
790
801
|
return
|
791
802
|
|
@@ -794,12 +805,12 @@ def _download_file(target_url, store_path, file_name, cover=False):
|
|
794
805
|
if not use_idm:
|
795
806
|
python_downloader = _HycomDownloader([(target_url, save_path)])
|
796
807
|
asyncio.run(python_downloader.run())
|
797
|
-
time.sleep(3 + random.uniform(0, 10))
|
808
|
+
# time.sleep(3 + random.uniform(0, 10))
|
798
809
|
else:
|
799
810
|
idm_downloader(target_url, store_path, file_name, given_idm_engine)
|
800
811
|
idm_download_list.append(save_path)
|
801
812
|
# print(f"[bold #3dfc40]File [bold #dfff73]{save_path} [#3dfc40]has been submit to IDM for downloading")
|
802
|
-
time.sleep(3 + random.uniform(0, 10))
|
813
|
+
# time.sleep(3 + random.uniform(0, 10))
|
803
814
|
|
804
815
|
|
805
816
|
def _check_hour_is_valid(ymdh_str):
|
@@ -890,7 +901,7 @@ def _prepare_url_to_download(var, lon_min=0, lon_max=359.92, lat_min=-80, lat_ma
|
|
890
901
|
else:
|
891
902
|
if download_time < "2024081012":
|
892
903
|
varlist = [_ for _ in var]
|
893
|
-
for key, value in pbar(var_group.items(), description=f"
|
904
|
+
for key, value in pbar(var_group.items(), description=f"Var Group {download_time} ->", total=len(var_group), color="#d7feb9", next_line=True):
|
894
905
|
current_group = []
|
895
906
|
for v in varlist:
|
896
907
|
if v in value:
|
@@ -912,7 +923,7 @@ def _prepare_url_to_download(var, lon_min=0, lon_max=359.92, lat_min=-80, lat_ma
|
|
912
923
|
file_name = f"HYCOM_{key}_{download_time}-{download_time_end}.nc"
|
913
924
|
_download_file(submit_url, store_path, file_name, cover)
|
914
925
|
else:
|
915
|
-
for v in pbar(var,description=f
|
926
|
+
for v in pbar(var, description=f"Var {download_time} ->", total=len(var), color="#d7feb9", next_line=True):
|
916
927
|
submit_url = _get_submit_url_var(v, depth, level_num, lon_min, lon_max, lat_min, lat_max, dataset_name, version_name, download_time, download_time_end)
|
917
928
|
file_name = f"HYCOM_{variable_info[v]['var_name']}_{download_time}.nc"
|
918
929
|
if download_time_end is not None:
|
oafuncs/oa_draw.py
CHANGED
@@ -15,6 +15,7 @@ Python Version: 3.11
|
|
15
15
|
|
16
16
|
import warnings
|
17
17
|
|
18
|
+
import cv2
|
18
19
|
import cartopy.crs as ccrs
|
19
20
|
import cartopy.feature as cfeature
|
20
21
|
import matplotlib as mpl
|
@@ -132,6 +133,70 @@ def gif(image_paths: list[str], output_gif_name: str, frame_duration: float = 20
|
|
132
133
|
return
|
133
134
|
|
134
135
|
|
136
|
+
def movie(image_files, output_video_path, fps):
|
137
|
+
"""
|
138
|
+
从图像文件列表创建视频。
|
139
|
+
|
140
|
+
Args:
|
141
|
+
image_files (list): 按顺序排列的图像文件路径列表。
|
142
|
+
output_video_path (str): 输出视频文件的路径 (例如 'output.mp4')。
|
143
|
+
fps (int): 视频的帧率。
|
144
|
+
"""
|
145
|
+
if not image_files:
|
146
|
+
print("错误:图像文件列表为空。")
|
147
|
+
return
|
148
|
+
|
149
|
+
# 读取第一张图片以获取帧尺寸
|
150
|
+
try:
|
151
|
+
frame = cv2.imread(image_files[0])
|
152
|
+
if frame is None:
|
153
|
+
print(f"错误:无法读取第一张图片:{image_files[0]}")
|
154
|
+
return
|
155
|
+
height, width, layers = frame.shape
|
156
|
+
size = (width, height)
|
157
|
+
print(f"视频尺寸设置为:{size}")
|
158
|
+
except Exception as e:
|
159
|
+
print(f"读取第一张图片时出错:{e}")
|
160
|
+
return
|
161
|
+
|
162
|
+
# 选择编解码器并创建VideoWriter对象
|
163
|
+
# 对于 .mp4 文件,常用 'mp4v' 或 'avc1'
|
164
|
+
# 对于 .avi 文件,常用 'XVID' 或 'MJPG'
|
165
|
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v") # 或者尝试 'avc1', 'XVID' 等
|
166
|
+
out = cv2.VideoWriter(output_video_path, fourcc, fps, size)
|
167
|
+
|
168
|
+
if not out.isOpened():
|
169
|
+
print(f"错误:无法打开视频文件进行写入:{output_video_path}")
|
170
|
+
print("请检查编解码器 ('fourcc') 是否受支持以及路径是否有效。")
|
171
|
+
return
|
172
|
+
|
173
|
+
print(f"开始将图像写入视频:{output_video_path}...")
|
174
|
+
for i, filename in enumerate(image_files):
|
175
|
+
try:
|
176
|
+
frame = cv2.imread(filename)
|
177
|
+
if frame is None:
|
178
|
+
print(f"警告:跳过无法读取的图像:{filename}")
|
179
|
+
continue
|
180
|
+
# 确保帧尺寸与初始化时相同,如果需要可以调整大小
|
181
|
+
current_height, current_width, _ = frame.shape
|
182
|
+
if (current_width, current_height) != size:
|
183
|
+
print(f"警告:图像 {filename} 的尺寸 ({current_width}, {current_height}) 与初始尺寸 {size} 不同。将调整大小。")
|
184
|
+
frame = cv2.resize(frame, size)
|
185
|
+
|
186
|
+
out.write(frame)
|
187
|
+
# 打印进度(可选)
|
188
|
+
if (i + 1) % 50 == 0 or (i + 1) == len(image_files):
|
189
|
+
print(f"已处理 {i + 1}/{len(image_files)} 帧")
|
190
|
+
|
191
|
+
except Exception as e:
|
192
|
+
print(f"处理图像 {filename} 时出错:{e}")
|
193
|
+
continue # 跳过有问题的帧
|
194
|
+
|
195
|
+
# 释放资源
|
196
|
+
out.release()
|
197
|
+
print(f"视频创建成功:{output_video_path}")
|
198
|
+
|
199
|
+
|
135
200
|
def add_lonlat_unit(longitudes: list[float] = None, latitudes: list[float] = None, decimal_places: int = 2) -> tuple[list[str], list[str]] | list[str]:
|
136
201
|
"""Convert longitude and latitude values to formatted string labels.
|
137
202
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: oafuncs
|
3
|
-
Version: 0.0.98.
|
3
|
+
Version: 0.0.98.8
|
4
4
|
Summary: Oceanic and Atmospheric Functions
|
5
5
|
Home-page: https://github.com/Industry-Pays/OAFuncs
|
6
6
|
Author: Kun Liu
|
@@ -27,6 +27,7 @@ Requires-Dist: requests
|
|
27
27
|
Requires-Dist: bs4
|
28
28
|
Requires-Dist: httpx
|
29
29
|
Requires-Dist: matplotlib
|
30
|
+
Requires-Dist: opencv-python
|
30
31
|
Requires-Dist: netCDF4
|
31
32
|
Requires-Dist: xlrd
|
32
33
|
Requires-Dist: geopandas
|
@@ -1,8 +1,8 @@
|
|
1
1
|
oafuncs/__init__.py,sha256=T_-VtnWWllV3Q91twT5Yt2sUapeA051QbPNnBxmg9nw,1456
|
2
2
|
oafuncs/oa_cmap.py,sha256=DimWT4Bg7uE5Lx8hSw1REp7whpsR2pFRStAwk1cowEM,11494
|
3
|
-
oafuncs/oa_data.py,sha256=
|
3
|
+
oafuncs/oa_data.py,sha256=7heyoFOBt_xqe0YSiUdO6tOpmySm0FuG1fHSoAO1NJI,10271
|
4
4
|
oafuncs/oa_date.py,sha256=KqU-bHtC74hYsf6VgiA3i2vI__q_toOVR-whFy4cYP8,5523
|
5
|
-
oafuncs/oa_draw.py,sha256=
|
5
|
+
oafuncs/oa_draw.py,sha256=Wj2QBgyIPpV_dxaDrH10jqj_puK9ZM9rd-si-3VrsrE,17631
|
6
6
|
oafuncs/oa_file.py,sha256=goF5iRXJFFCIKhIjlkCnYYt0EYlJb_4r8AeYNZ0-SOk,16209
|
7
7
|
oafuncs/oa_help.py,sha256=_4AZgRDq5Or0vauNvq5IDDHIBoBfdOQtzak-mG1wwAw,4537
|
8
8
|
oafuncs/oa_nc.py,sha256=L1gqXxg93kIDsMOa87M0o-53KVmdqCipnXeF9XfzfY8,10513
|
@@ -21,7 +21,7 @@ oafuncs/_script/plot_dataset.py,sha256=zkSEnO_-biyagorwWXPoihts_cwuvripzEt-l9bHJ
|
|
21
21
|
oafuncs/_script/replace_file_content.py,sha256=eCFZjnZcwyRvy6b4mmIfBna-kylSZTyJRfgXd6DdCjk,5982
|
22
22
|
oafuncs/oa_down/User_Agent-list.txt,sha256=pHaMlElMvZ8TG4vf4BqkZYKqe0JIGkr4kCN0lM1Y9FQ,514295
|
23
23
|
oafuncs/oa_down/__init__.py,sha256=kRX5eTUCbAiz3zTaQM1501paOYS_3fizDN4Pa0mtNUA,585
|
24
|
-
oafuncs/oa_down/hycom_3hourly.py,sha256=
|
24
|
+
oafuncs/oa_down/hycom_3hourly.py,sha256=wWV14-OB9_LMmjUiZr3YXWBdKKwAyGXNa3Up7fSiWwk,55553
|
25
25
|
oafuncs/oa_down/hycom_3hourly_proxy.py,sha256=1eaoJGI_m-7w4ZZ3n7NGxkZaeFdsm0d3U-hyw8RFNbc,54563
|
26
26
|
oafuncs/oa_down/idm.py,sha256=4z5IvgfTyIKEI1kOtqXZwN7Jnfjwp6qDBOIoVyOLp0I,1823
|
27
27
|
oafuncs/oa_down/literature.py,sha256=2bF9gSKQbzcci9LcKE81j8JEjIJwON7jbwQB3gDDA3E,11331
|
@@ -37,8 +37,8 @@ oafuncs/oa_sign/__init__.py,sha256=QKqTFrJDFK40C5uvk48GlRRbGFzO40rgkYwu6dYxatM,5
|
|
37
37
|
oafuncs/oa_sign/meteorological.py,sha256=8091SHo2L8kl4dCFmmSH5NGVHDku5i5lSiLEG5DLnOQ,6489
|
38
38
|
oafuncs/oa_sign/ocean.py,sha256=xrW-rWD7xBWsB5PuCyEwQ1Q_RDKq2KCLz-LOONHgldU,5932
|
39
39
|
oafuncs/oa_sign/scientific.py,sha256=a4JxOBgm9vzNZKpJ_GQIQf7cokkraV5nh23HGbmTYKw,5064
|
40
|
-
oafuncs-0.0.98.
|
41
|
-
oafuncs-0.0.98.
|
42
|
-
oafuncs-0.0.98.
|
43
|
-
oafuncs-0.0.98.
|
44
|
-
oafuncs-0.0.98.
|
40
|
+
oafuncs-0.0.98.8.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
|
41
|
+
oafuncs-0.0.98.8.dist-info/METADATA,sha256=Le6ieydYuvZciK8CaFHPBUy47T3geyA48Z6nzSbLMwQ,4272
|
42
|
+
oafuncs-0.0.98.8.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
43
|
+
oafuncs-0.0.98.8.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
|
44
|
+
oafuncs-0.0.98.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|