skyborn 0.1.0__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.
skyborn-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.2
2
+ Name: skyborn
3
+ Version: 0.1.0
4
+ Summary: Atmospheric science research utilities
5
+ Author: Qianye Su
6
+ Author-email: suqianye2000@gmail.com
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Dist: numpy>=1.20.0
12
+ Requires-Dist: xarray>=0.19.0
13
+ Requires-Dist: matplotlib>=3.4.0
14
+ Requires-Dist: cartopy>=0.20.0
15
+ Requires-Dist: netCDF4>=1.5.7
16
+ Requires-Dist: metpy>=1.1.0
17
+ Requires-Dist: cfgrib>=0.9.9
18
+ Requires-Dist: eccodes>=1.4.0
19
+ Requires-Dist: scikit-learn>=1.0.0
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: license
25
+ Dynamic: requires-dist
26
+ Dynamic: summary
27
+
28
+ Skyborn is a tool for easy plotting ERA5 weather data.
@@ -0,0 +1,11 @@
1
+ # Skyborn: A Collection of Tools for My Research
2
+
3
+ Skyborn is a collection of functions I use in my general research, primarily for personal use.
4
+
5
+ ## Installation
6
+
7
+ To install the Skyborn package, you can use pip:
8
+
9
+ ```bash
10
+ pip install skyborn
11
+ ```
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
skyborn-0.1.0/setup.py ADDED
@@ -0,0 +1,30 @@
1
+ import setuptools
2
+
3
+ setuptools.setup(
4
+ name="skyborn",
5
+ version="0.1.0", # 与pyproject.toml保持一致
6
+ author="Qianye Su",
7
+ author_email="suqianye2000@gmail.com",
8
+ description="Atmospheric science research utilities",
9
+ long_description="Skyborn is a tool for easy plotting ERA5 weather data.",
10
+ license="MIT",
11
+ packages=setuptools.find_packages(where="src"),
12
+ package_dir={"": "src"},
13
+ classifiers=[
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ ],
18
+ license_files=("LICENSE.txt"),
19
+ install_requires=[
20
+ "numpy>=1.20.0",
21
+ "xarray>=0.19.0",
22
+ "matplotlib>=3.4.0",
23
+ "cartopy>=0.20.0",
24
+ "netCDF4>=1.5.7",
25
+ "metpy>=1.1.0",
26
+ "cfgrib>=0.9.9",
27
+ "eccodes>=1.4.0",
28
+ "scikit-learn>=1.0.0"
29
+ ]
30
+ )
@@ -0,0 +1,21 @@
1
+ """
2
+ Skyborn 大气科学研究工具库
3
+
4
+ 提供大气科学常用数据处理、分析和可视化功能模块
5
+ """
6
+
7
+ from .io import read_netcdf, read_grib
8
+ from .calculations import (
9
+ convert_longitude_range,
10
+ linearRegression
11
+ )
12
+ from .plotting import add_equal_axes, createFigure
13
+ from .gradients import (
14
+ calculate_gradient,
15
+ calculate_meridional_gradient,
16
+ calculate_zonal_gradient,
17
+ calculate_vertical_gradient
18
+ )
19
+
20
+ __version__ = "0.1.0"
21
+ __all__ = ['io', 'calculations', 'gradients', 'plotting']
@@ -0,0 +1,54 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ import metpy.calc as mpcalc
4
+ from metpy.units import units
5
+ from typing import Tuple, Union
6
+ from sklearn.feature_selection import f_regression
7
+
8
+
9
+ def linearRegression(mat, sequence):
10
+ '''
11
+ 回归函数 第一个是需要回归的变量,第二个为一个数组
12
+ '''
13
+ if len(mat) != len(sequence):
14
+ raise ValueError('Data, array must be must be equal!!!!!!!'
15
+ ' %s and %s' % (mat.shape[0], len(sequence)))
16
+ A = np.column_stack((sequence, np.ones(sequence.shape[0])))
17
+ (a, b, c) = mat.shape
18
+ mat_rshp = mat.reshape((a, b*c))
19
+ mat_rshp_reg = np.linalg.lstsq(A, mat_rshp, rcond=None)[0][0]
20
+ mat_reg = mat_rshp_reg.reshape((b, c))
21
+ pvalue = f_regression(mat_rshp, sequence)[1].reshape(b, c)
22
+ return mat_reg, pvalue
23
+
24
+
25
+ def convert_longitude_range(data: Union[xr.DataArray, xr.Dataset],
26
+ lon: str = 'lon',
27
+ center_on_180: bool = True
28
+ ) -> Union[xr.DataArray, xr.Dataset]:
29
+ '''
30
+ Wrap longitude coordinates of DataArray or Dataset to either -180..179 or 0..359.
31
+
32
+ Parameters
33
+ ----------
34
+ data : xr.DataArray or xr.Dataset
35
+ An xarray DataArray or Dataset object containing longitude coordinates.
36
+ lon : str, optional
37
+ The name of the longitude coordinate, default is 'lon'.
38
+ center_on_180 : bool, optional
39
+ If True, wrap longitude from 0..359 to -180..179;
40
+ If False, wrap longitude from -180..179 to 0..359.
41
+
42
+ Returns
43
+ -------
44
+ xr.DataArray or xr.Dataset
45
+ The DataArray or Dataset with wrapped longitude coordinates.
46
+ '''
47
+ # Wrap -180..179 to 0..359
48
+ if center_on_180:
49
+ data = data.assign_coords(**{lon: (lambda x: (x[lon] % 360))})
50
+ # Wrap 0..359 to -180..179
51
+ else:
52
+ data = data.assign_coords(
53
+ **{lon: (lambda x: ((x[lon] + 180) % 360) - 180)})
54
+ return data.sortby(lon, ascending=True)
@@ -0,0 +1,189 @@
1
+ import numpy as np
2
+ from typing import Union, Optional
3
+
4
+ EARTH_RADIUS = 6371e3 # 地球平均半径 (m)
5
+
6
+
7
+ def calculate_gradient(field: np.ndarray, coordinates: np.ndarray,
8
+ axis: int = -1, radius: float = 6371000.0) -> np.ndarray:
9
+ """计算任意维度数组沿指定坐标的梯度
10
+
11
+ Args:
12
+ field: 待计算梯度的数据场,可以是任意维度的数组,如(time, level, lat, lon)
13
+ coordinates: 沿着计算梯度的坐标数组,如纬度或经度值
14
+ axis: 指定计算梯度的维度轴,默认为最后一个维度(-1)
15
+ radius: 地球半径,默认为6371000.0米,用于纬度梯度计算
16
+
17
+ Returns:
18
+ 与输入场相同形状的梯度场
19
+ """
20
+ # 检查输入数据维度是否匹配
21
+ if coordinates.size != field.shape[axis]:
22
+ raise ValueError(
23
+ f"坐标数组大小({coordinates.size})与场数据在指定轴上的大小({field.shape[axis]})不匹配")
24
+
25
+ # 确定是经度还是纬度坐标
26
+ is_latitude = False
27
+ if np.min(coordinates) >= -90 and np.max(coordinates) <= 90:
28
+ is_latitude = True
29
+ # 对于纬度,计算实际距离(单位:米)
30
+ if is_latitude:
31
+ # 将纬度转换为实际距离
32
+ distances = coordinates * np.pi / 180.0 * radius
33
+ else:
34
+ # 对于经度,我们需要考虑纬度的影响,但这需要额外的纬度信息
35
+ # 这里简单处理为直接使用经度差
36
+ distances = coordinates
37
+
38
+ # 创建与输入相同形状的输出数组
39
+ gradient = np.zeros_like(field, dtype=float)
40
+
41
+ # 为了使用numpy的高级索引,我们需要创建索引数组
42
+ ndim = field.ndim
43
+ idx_ranges = [slice(None)] * ndim
44
+
45
+ # 对内部点使用中心差分
46
+ inner_range = slice(1, field.shape[axis]-1)
47
+ idx_forward = idx_ranges.copy()
48
+ idx_forward[axis] = slice(2, field.shape[axis])
49
+
50
+ idx_center = idx_ranges.copy()
51
+ idx_center[axis] = inner_range
52
+
53
+ idx_backward = idx_ranges.copy()
54
+ idx_backward[axis] = slice(0, field.shape[axis]-2)
55
+
56
+ # 使用矢量化操作计算内部点的梯度
57
+ forward_dists = np.diff(distances[1:])
58
+ backward_dists = np.diff(distances[:-1])
59
+ total_dists = distances[2:] - distances[:-2]
60
+
61
+ # 创建系数数组,形状适合广播
62
+ shape = [1] * ndim
63
+ shape[axis] = len(forward_dists)
64
+
65
+ a0 = forward_dists.reshape(shape)
66
+ b0 = backward_dists.reshape(shape)
67
+ c0 = total_dists.reshape(shape)
68
+
69
+ # 使用加权差分公式计算梯度
70
+ gradient[tuple(idx_center)] = (
71
+ b0 / a0 / c0 * field[tuple(idx_forward)] -
72
+ a0 / b0 / c0 * field[tuple(idx_backward)] +
73
+ (a0 - b0) / a0 / b0 * field[tuple(idx_center)]
74
+ )
75
+
76
+ # 处理边界点(前向和后向差分)
77
+ # 左边界
78
+ left_idx = idx_ranges.copy()
79
+ left_idx[axis] = 0
80
+ left_idx_plus = idx_ranges.copy()
81
+ left_idx_plus[axis] = 1
82
+ gradient[tuple(left_idx)] = (field[tuple(left_idx_plus)] -
83
+ field[tuple(left_idx)]) / (distances[1] - distances[0])
84
+
85
+ # 右边界
86
+ right_idx = idx_ranges.copy()
87
+ right_idx[axis] = -1
88
+ right_idx_minus = idx_ranges.copy()
89
+ right_idx_minus[axis] = -2
90
+ gradient[tuple(right_idx)] = (field[tuple(right_idx)] -
91
+ field[tuple(right_idx_minus)]) / (distances[-1] - distances[-2])
92
+
93
+ return gradient
94
+
95
+
96
+ def calculate_meridional_gradient(field: np.ndarray, latitudes: np.ndarray,
97
+ lat_axis: int = -1, radius: float = 6371000.0) -> np.ndarray:
98
+ """计算经向梯度(沿纬度方向的梯度)
99
+
100
+ Args:
101
+ field: 待计算梯度的数据场,可以是任意维度的数组
102
+ latitudes: 纬度数组(度)
103
+ lat_axis: 指定纬度所在的轴,默认为最后一个维度(-1)
104
+ radius: 地球半径,默认为6371000.0米
105
+
106
+ Returns:
107
+ 经向梯度场
108
+ """
109
+ return calculate_gradient(field, latitudes, axis=lat_axis, radius=radius)
110
+
111
+
112
+ def calculate_vertical_gradient(field: np.ndarray,
113
+ pressure: np.ndarray,
114
+ pressure_axis: int = -3) -> np.ndarray:
115
+ """计算垂直梯度(沿气压方向的梯度)
116
+
117
+ Args:
118
+ field: 待计算梯度的数据场
119
+ pressure: 气压数组(Pa),必须为单调递减
120
+ pressure_axis: 指定气压所在的轴,默认为倒数第三个维度(-3)
121
+
122
+ Returns:
123
+ 垂直梯度场
124
+ """
125
+ return calculate_gradient(field, pressure, axis=pressure_axis, radius=None)
126
+
127
+
128
+ def calculate_zonal_gradient(field: np.ndarray, longitudes: np.ndarray, latitudes: np.ndarray,
129
+ lon_axis: int = -1, lat_axis: int = -2, radius: float = 6371000.0) -> np.ndarray:
130
+ """计算纬向梯度(沿经度方向的梯度)
131
+
132
+ Args:
133
+ field: 待计算梯度的数据场,可以是任意维度的数组
134
+ longitudes: 经度数组(度)
135
+ latitudes: 纬度数组(度),用于计算不同纬度下经度的实际距离
136
+ lon_axis: 指定经度所在的轴,默认为最后一个维度(-1)
137
+ lat_axis: 指定纬度所在的轴,默认为倒数第二个维度(-2)
138
+ radius: 地球半径,默认为6371000.0米
139
+
140
+ Returns:
141
+ 纬向梯度场
142
+ """
143
+ # 获取纬度因子,用于调整不同纬度下经度间的实际距离
144
+ cos_lat = np.cos(np.radians(latitudes))
145
+
146
+ # 如果场是4D (time, level, lat, lon)
147
+ if field.ndim == 4 and lon_axis == -1 and lat_axis == -2:
148
+ # 创建一个广播形状的纬度因子数组
149
+ cos_lat_expanded = cos_lat.reshape(1, 1, -1, 1)
150
+
151
+ # 将经度转换为考虑纬度的实际距离
152
+ effective_distances = np.radians(
153
+ longitudes) * radius * cos_lat_expanded
154
+
155
+ # 现在计算梯度
156
+ return calculate_gradient(field, effective_distances, axis=lon_axis, radius=1.0)
157
+
158
+ # 如果场是3D (time, lat, lon)
159
+ elif field.ndim == 3 and lon_axis == -1 and lat_axis == -2:
160
+ cos_lat_expanded = cos_lat.reshape(1, -1, 1)
161
+ effective_distances = np.radians(
162
+ longitudes) * radius * cos_lat_expanded
163
+ return calculate_gradient(field, effective_distances, axis=lon_axis, radius=1.0)
164
+
165
+ else:
166
+ # 对于其他维度组合,需要创建适当的广播形状
167
+ broadcast_shape = [1] * field.ndim
168
+ broadcast_shape[lat_axis] = len(latitudes)
169
+ cos_lat_expanded = cos_lat.reshape(broadcast_shape)
170
+
171
+ # 创建有效距离数组
172
+ effective_longitudes = np.radians(longitudes) * radius
173
+
174
+ # 对于每个纬度,计算梯度
175
+ result = np.zeros_like(field)
176
+
177
+ # 循环处理每个纬度(这部分实现取决于具体数据结构,可能需要调整)
178
+ for i in range(len(latitudes)):
179
+ idx = [slice(None)] * field.ndim
180
+ idx[lat_axis] = i
181
+
182
+ # 调整当前纬度的经度距离
183
+ current_effective_dist = effective_longitudes * cos_lat[i]
184
+
185
+ # 计算当前纬度的梯度
186
+ result[tuple(idx)] = calculate_gradient(
187
+ field[tuple(idx)], current_effective_dist, axis=lon_axis, radius=1.0)
188
+
189
+ return result
@@ -0,0 +1,25 @@
1
+ import xarray as xr
2
+ import cfgrib
3
+
4
+ def read_netcdf(file_path: str) -> xr.Dataset:
5
+ """读取NetCDF格式气象数据
6
+
7
+ Args:
8
+ file_path: 文件路径
9
+
10
+ Returns:
11
+ xarray Dataset对象
12
+ """
13
+ return xr.open_dataset(file_path)
14
+
15
+ def read_grib(file_path: str, **kwargs) -> xr.Dataset:
16
+ """读取GRIB格式气象数据
17
+
18
+ Args:
19
+ file_path: 文件路径
20
+ kwargs: 传递给cfgrib.open_dataset的额外参数
21
+
22
+ Returns:
23
+ xarray Dataset对象
24
+ """
25
+ return cfgrib.open_dataset(file_path, **kwargs)
@@ -0,0 +1,67 @@
1
+ import matplotlib.pyplot as plt
2
+ import matplotlib.transforms as mtransforms
3
+ import numpy as np
4
+
5
+ def add_equal_axes(ax, loc, pad, width):
6
+ '''
7
+ 在原有的Axes旁新添一个等高或等宽的Axes并返回该对象.
8
+
9
+ Parameters
10
+ ----------
11
+ ax : Axes or array_like of Axes
12
+ 原有的Axes,也可以为一组Axes构成的数组.
13
+
14
+ loc : {'left', 'right', 'bottom', 'top'}
15
+ 新Axes相对于旧Axes的位置.
16
+
17
+ pad : float
18
+ 新Axes与旧Axes的间距.
19
+
20
+ width: float
21
+ 当loc='left'或'right'时,width表示新Axes的宽度.
22
+ 当loc='bottom'或'top'时,width表示新Axes的高度.
23
+
24
+ Returns
25
+ -------
26
+ ax_new : Axes
27
+ 新Axes对象.
28
+ '''
29
+ # 无论ax是单个还是一组Axes,获取ax的大小位置.
30
+ axes = np.atleast_1d(ax).ravel()
31
+ bbox = mtransforms.Bbox.union([ax.get_position() for ax in axes])
32
+
33
+ # 决定新Axes的大小位置.
34
+ if loc == 'left':
35
+ x0_new = bbox.x0 - pad - width
36
+ x1_new = x0_new + width
37
+ y0_new = bbox.y0
38
+ y1_new = bbox.y1
39
+ elif loc == 'right':
40
+ x0_new = bbox.x1 + pad
41
+ x1_new = x0_new + width
42
+ y0_new = bbox.y0
43
+ y1_new = bbox.y1
44
+ elif loc == 'bottom':
45
+ x0_new = bbox.x0
46
+ x1_new = bbox.x1
47
+ y0_new = bbox.y0 - pad - width
48
+ y1_new = y0_new + width
49
+ elif loc == 'top':
50
+ x0_new = bbox.x0
51
+ x1_new = bbox.x1
52
+ y0_new = bbox.y1 + pad
53
+ y1_new = y0_new + width
54
+
55
+ # 创建新Axes.
56
+ fig = axes[0].get_figure()
57
+ bbox_new = mtransforms.Bbox.from_extents(x0_new, y0_new, x1_new, y1_new)
58
+ ax_new = fig.add_axes(bbox_new)
59
+
60
+ return ax_new
61
+
62
+ def createFigure(figsize=(12, 8), dpi=300, subplotAdj=None, **kwargs):
63
+ figsize = figsize
64
+ figure = plt.figure(figsize=figsize, dpi=dpi, **kwargs)
65
+ if subplotAdj is not None:
66
+ plt.subplots_adjust(**subplotAdj)
67
+ return figure
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.2
2
+ Name: skyborn
3
+ Version: 0.1.0
4
+ Summary: Atmospheric science research utilities
5
+ Author: Qianye Su
6
+ Author-email: suqianye2000@gmail.com
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Dist: numpy>=1.20.0
12
+ Requires-Dist: xarray>=0.19.0
13
+ Requires-Dist: matplotlib>=3.4.0
14
+ Requires-Dist: cartopy>=0.20.0
15
+ Requires-Dist: netCDF4>=1.5.7
16
+ Requires-Dist: metpy>=1.1.0
17
+ Requires-Dist: cfgrib>=0.9.9
18
+ Requires-Dist: eccodes>=1.4.0
19
+ Requires-Dist: scikit-learn>=1.0.0
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: license
25
+ Dynamic: requires-dist
26
+ Dynamic: summary
27
+
28
+ Skyborn is a tool for easy plotting ERA5 weather data.
@@ -0,0 +1,12 @@
1
+ README.md
2
+ setup.py
3
+ src/skyborn/__init__.py
4
+ src/skyborn/calculations.py
5
+ src/skyborn/gradients.py
6
+ src/skyborn/io.py
7
+ src/skyborn/plotting.py
8
+ src/skyborn.egg-info/PKG-INFO
9
+ src/skyborn.egg-info/SOURCES.txt
10
+ src/skyborn.egg-info/dependency_links.txt
11
+ src/skyborn.egg-info/requires.txt
12
+ src/skyborn.egg-info/top_level.txt
@@ -0,0 +1,9 @@
1
+ numpy>=1.20.0
2
+ xarray>=0.19.0
3
+ matplotlib>=3.4.0
4
+ cartopy>=0.20.0
5
+ netCDF4>=1.5.7
6
+ metpy>=1.1.0
7
+ cfgrib>=0.9.9
8
+ eccodes>=1.4.0
9
+ scikit-learn>=1.0.0
@@ -0,0 +1 @@
1
+ skyborn