AeroViz 0.1.21__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.
Files changed (180) hide show
  1. AeroViz/__init__.py +13 -0
  2. AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
  3. AeroViz/data/DEFAULT_DATA.csv +1417 -0
  4. AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
  5. AeroViz/data/hysplit_example_data.txt +101 -0
  6. AeroViz/dataProcess/Chemistry/__init__.py +149 -0
  7. AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
  8. AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
  9. AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
  10. AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
  11. AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
  12. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  13. AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  14. AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
  15. AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
  16. AeroViz/dataProcess/Optical/__init__.py +281 -0
  17. AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
  18. AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
  19. AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
  20. AeroViz/dataProcess/Optical/_derived.py +518 -0
  21. AeroViz/dataProcess/Optical/_extinction.py +123 -0
  22. AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
  23. AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
  24. AeroViz/dataProcess/Optical/coefficient.py +72 -0
  25. AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  26. AeroViz/dataProcess/Optical/mie_theory.py +260 -0
  27. AeroViz/dataProcess/README.md +271 -0
  28. AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
  29. AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
  30. AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
  31. AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
  32. AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
  33. AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
  34. AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
  35. AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
  36. AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
  37. AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
  38. AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
  39. AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
  40. AeroViz/dataProcess/SizeDistr/prop.py +62 -0
  41. AeroViz/dataProcess/VOC/__init__.py +14 -0
  42. AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
  43. AeroViz/dataProcess/VOC/_potential_par.py +108 -0
  44. AeroViz/dataProcess/VOC/support_voc.json +446 -0
  45. AeroViz/dataProcess/__init__.py +66 -0
  46. AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
  47. AeroViz/dataProcess/core/__init__.py +272 -0
  48. AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
  49. AeroViz/mcp_server.py +352 -0
  50. AeroViz/plot/__init__.py +13 -0
  51. AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
  52. AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
  53. AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
  54. AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
  55. AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
  56. AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
  57. AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
  58. AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
  59. AeroViz/plot/bar.py +126 -0
  60. AeroViz/plot/box.py +69 -0
  61. AeroViz/plot/distribution/__init__.py +1 -0
  62. AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
  63. AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
  64. AeroViz/plot/distribution/distribution.py +576 -0
  65. AeroViz/plot/meteorology/CBPF.py +295 -0
  66. AeroViz/plot/meteorology/__init__.py +3 -0
  67. AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
  68. AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
  69. AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
  70. AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
  71. AeroViz/plot/meteorology/hysplit.py +93 -0
  72. AeroViz/plot/meteorology/wind_rose.py +77 -0
  73. AeroViz/plot/optical/__init__.py +1 -0
  74. AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
  75. AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
  76. AeroViz/plot/optical/optical.py +388 -0
  77. AeroViz/plot/pie.py +210 -0
  78. AeroViz/plot/radar.py +184 -0
  79. AeroViz/plot/regression.py +200 -0
  80. AeroViz/plot/scatter.py +174 -0
  81. AeroViz/plot/templates/__init__.py +6 -0
  82. AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  83. AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
  84. AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
  85. AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
  86. AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
  87. AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
  88. AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
  89. AeroViz/plot/templates/ammonium_rich.py +34 -0
  90. AeroViz/plot/templates/contour.py +47 -0
  91. AeroViz/plot/templates/corr_matrix.py +267 -0
  92. AeroViz/plot/templates/diurnal_pattern.py +61 -0
  93. AeroViz/plot/templates/koschmieder.py +95 -0
  94. AeroViz/plot/templates/metal_heatmap.py +164 -0
  95. AeroViz/plot/timeseries/__init__.py +2 -0
  96. AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
  97. AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
  98. AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
  99. AeroViz/plot/timeseries/template.py +47 -0
  100. AeroViz/plot/timeseries/timeseries.py +446 -0
  101. AeroViz/plot/utils/__init__.py +4 -0
  102. AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  103. AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
  104. AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
  105. AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
  106. AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
  107. AeroViz/plot/utils/_color.py +71 -0
  108. AeroViz/plot/utils/_unit.py +55 -0
  109. AeroViz/plot/utils/fRH.json +390 -0
  110. AeroViz/plot/utils/plt_utils.py +92 -0
  111. AeroViz/plot/utils/sklearn_utils.py +49 -0
  112. AeroViz/plot/utils/units.json +89 -0
  113. AeroViz/plot/violin.py +80 -0
  114. AeroViz/rawDataReader/FLOW.md +138 -0
  115. AeroViz/rawDataReader/__init__.py +220 -0
  116. AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
  117. AeroViz/rawDataReader/config/__init__.py +0 -0
  118. AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
  119. AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
  120. AeroViz/rawDataReader/config/supported_instruments.py +135 -0
  121. AeroViz/rawDataReader/core/__init__.py +658 -0
  122. AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
  123. AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
  124. AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
  125. AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
  126. AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
  127. AeroViz/rawDataReader/core/logger.py +171 -0
  128. AeroViz/rawDataReader/core/pre_process.py +308 -0
  129. AeroViz/rawDataReader/core/qc.py +961 -0
  130. AeroViz/rawDataReader/core/report.py +579 -0
  131. AeroViz/rawDataReader/script/AE33.py +173 -0
  132. AeroViz/rawDataReader/script/AE43.py +151 -0
  133. AeroViz/rawDataReader/script/APS.py +339 -0
  134. AeroViz/rawDataReader/script/Aurora.py +191 -0
  135. AeroViz/rawDataReader/script/BAM1020.py +90 -0
  136. AeroViz/rawDataReader/script/BC1054.py +161 -0
  137. AeroViz/rawDataReader/script/EPA.py +79 -0
  138. AeroViz/rawDataReader/script/GRIMM.py +68 -0
  139. AeroViz/rawDataReader/script/IGAC.py +140 -0
  140. AeroViz/rawDataReader/script/MA350.py +179 -0
  141. AeroViz/rawDataReader/script/Minion.py +218 -0
  142. AeroViz/rawDataReader/script/NEPH.py +199 -0
  143. AeroViz/rawDataReader/script/OCEC.py +173 -0
  144. AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
  145. AeroViz/rawDataReader/script/SMPS.py +389 -0
  146. AeroViz/rawDataReader/script/TEOM.py +181 -0
  147. AeroViz/rawDataReader/script/VOC.py +106 -0
  148. AeroViz/rawDataReader/script/Xact.py +244 -0
  149. AeroViz/rawDataReader/script/__init__.py +28 -0
  150. AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
  151. AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
  152. AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
  153. AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
  154. AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
  155. AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
  156. AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
  157. AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
  158. AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
  159. AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
  160. AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
  161. AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
  162. AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
  163. AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
  164. AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
  165. AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
  166. AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
  167. AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
  168. AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
  169. AeroViz/tools/__init__.py +2 -0
  170. AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  171. AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
  172. AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
  173. AeroViz/tools/database.py +95 -0
  174. AeroViz/tools/dataclassifier.py +117 -0
  175. AeroViz/tools/dataprinter.py +58 -0
  176. aeroviz-0.1.21.dist-info/METADATA +294 -0
  177. aeroviz-0.1.21.dist-info/RECORD +180 -0
  178. aeroviz-0.1.21.dist-info/WHEEL +5 -0
  179. aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
  180. aeroviz-0.1.21.dist-info/top_level.txt +1 -0
@@ -0,0 +1,295 @@
1
+ import math
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ import pandas as pd
6
+ from matplotlib.pyplot import Figure, Axes
7
+ from pandas import DataFrame, Series
8
+ from scipy.ndimage import gaussian_filter
9
+
10
+ from AeroViz.plot.utils import *
11
+
12
+ __all__ = ['CBPF']
13
+
14
+
15
+ def improve_density_estimation(df, WS, WD, val, resolution=100, bandwidth=None):
16
+ """
17
+ 改進的密度估計函數,使用KDE方法來產生更平滑的分布
18
+
19
+ Parameters:
20
+ -----------
21
+ df : DataFrame
22
+ 包含風速風向數據的DataFrame
23
+ WS : str
24
+ 風速列名
25
+ WD : str
26
+ 風向列名
27
+ val : str
28
+ 要分析的變量列名
29
+ resolution : int
30
+ 網格解析度
31
+ bandwidth : float or tuple
32
+ KDE的頻寬參數,如果為None則自動選擇
33
+ """
34
+ from scipy.stats import gaussian_kde
35
+ import numpy as np
36
+
37
+ # 轉換為笛卡爾坐標
38
+ u = df[WS] * np.sin(np.radians(df[WD]))
39
+ v = df[WS] * np.cos(np.radians(df[WD]))
40
+
41
+ # 創建網格
42
+ u_range = np.linspace(u.min(), u.max(), resolution)
43
+ v_range = np.linspace(v.min(), v.max(), resolution)
44
+ U, V = np.meshgrid(u_range, v_range)
45
+
46
+ # 準備KDE的位置
47
+ positions = np.vstack([U.ravel(), V.ravel()])
48
+ values = np.vstack([u, v])
49
+
50
+ # 使用KDE進行密度估計
51
+ kernel = gaussian_kde(values, bw_method=bandwidth)
52
+ Z = np.reshape(kernel(positions), U.shape)
53
+
54
+ # 將密度值歸一化到[0,1]區間
55
+ Z = (Z - Z.min()) / (Z.max() - Z.min())
56
+
57
+ # 應用極坐標遮罩
58
+ center_u = len(u_range) // 2
59
+ center_v = len(v_range) // 2
60
+ max_radius = min(center_u, center_v)
61
+
62
+ Y, X = np.ogrid[-center_v:resolution - center_v, -center_u:resolution - center_u]
63
+ mask = X * X + Y * Y > max_radius * max_radius
64
+ Z[mask] = np.nan
65
+
66
+ return Z, U, V
67
+
68
+
69
+ def smooth_and_clean(Z, smooth_radius=2, min_density=1):
70
+ """
71
+ 平滑並清理密度圖,去除孤立點
72
+
73
+ Parameters:
74
+ -----------
75
+ Z : ndarray
76
+ 密度估計結果
77
+ smooth_radius : int
78
+ 平滑半徑
79
+ min_density : float
80
+ 最小密度閾值
81
+ """
82
+ from scipy.ndimage import gaussian_filter
83
+
84
+ # 先進行高斯平滑
85
+ Z_smooth = gaussian_filter(Z, sigma=smooth_radius)
86
+
87
+ # 去除低於閾值的點
88
+ # Z_smooth[Z_smooth < min_density] = np.nan
89
+
90
+ # 去除孤立點
91
+ rows, cols = Z_smooth.shape
92
+ for i in range(rows):
93
+ for j in range(cols):
94
+ if not np.isnan(Z_smooth[i, j]):
95
+ # 檢查周圍點
96
+ neighborhood = Z_smooth[
97
+ max(0, i - smooth_radius):min(rows, i + smooth_radius + 1),
98
+ max(0, j - smooth_radius):min(cols, j + smooth_radius + 1)
99
+ ]
100
+ if np.count_nonzero(~np.isnan(neighborhood)) < 1: # 如果周圍有效點太少
101
+ Z_smooth[i, j] = np.nan
102
+
103
+ return Z_smooth
104
+
105
+
106
+ def is_within_circle(center_row, center_col, row, col, radius):
107
+ return np.sqrt((center_row - row) ** 2 + (center_col - col) ** 2) <= radius
108
+
109
+
110
+ def remove_lonely_point(filtered_histogram, radius=4, magic_num=13):
111
+ rows, cols = filtered_histogram.shape
112
+ data_positions = np.where(~np.isnan(filtered_histogram))
113
+
114
+ for row, col in zip(*data_positions):
115
+ valid_data_count = 0
116
+ for i in range(max(0, row - radius), min(rows, row + radius + 1)):
117
+ for j in range(max(0, col - radius), min(cols, col + radius + 1)):
118
+ if (i, j) != (row, col) and is_within_circle(row, col, i, j, radius):
119
+ if not np.isnan(filtered_histogram[i, j]):
120
+ valid_data_count += 1
121
+
122
+ if valid_data_count <= magic_num:
123
+ filtered_histogram[row, col] = np.nan
124
+
125
+ return filtered_histogram
126
+
127
+
128
+ def fill_nan_with_mean(filtered_histogram, radius=4, magic_num=13):
129
+ rows, cols = filtered_histogram.shape
130
+ nan_positions = np.where(np.isnan(filtered_histogram))
131
+
132
+ for row, col in zip(*nan_positions):
133
+ surrounding_values = []
134
+ surrounding_values_within_one = []
135
+ nan_count = 0
136
+
137
+ for i in range(max(0, row - radius), min(rows, row + radius + 1)):
138
+ for j in range(max(0, col - radius), min(cols, col + radius + 1)):
139
+ if (i, j) != (row, col) and is_within_circle(row, col, i, j, radius):
140
+ if np.isnan(filtered_histogram[i, j]):
141
+ nan_count += 1
142
+ else:
143
+ surrounding_values.append(filtered_histogram[i, j])
144
+
145
+ for i in range(max(0, row - 2), min(rows, row + 2 + 1)):
146
+ for j in range(max(0, col - 2), min(cols, col + 2 + 1)):
147
+ if (i, j) != (row, col) and is_within_circle(row, col, i, j, 2):
148
+ if np.isnan(filtered_histogram[i, j]):
149
+ pass
150
+ else:
151
+ surrounding_values_within_one.append(filtered_histogram[i, j])
152
+
153
+ if nan_count < magic_num and surrounding_values_within_one:
154
+ filtered_histogram[row, col] = np.mean(surrounding_values)
155
+
156
+ return filtered_histogram
157
+
158
+
159
+ # TODO: fix the bug of the CBPF function
160
+ @set_figure(figsize=(4.3, 4))
161
+ def CBPF(df: DataFrame,
162
+ WS: Series | str,
163
+ WD: Series | str,
164
+ val: Series | str | None = None,
165
+ percentile: list | float | int | None = None,
166
+ max_ws: float | None = 5,
167
+ resolution: int = 50,
168
+ sigma: float | tuple = 2,
169
+ rlabel_pos: float = 30,
170
+ bottom_text: str | bool | None = None,
171
+ **kwargs
172
+ ) -> tuple[Figure, Axes]:
173
+ # conditional bivariate probability function (cbpf) python
174
+ # https://davidcarslaw.github.io/openair/reference/polarPlot.html
175
+ # https://github.com/davidcarslaw/openair/blob/master/R/polarPlot.R
176
+
177
+ df = df.dropna(subset=[WS, WD] + ([val] if val is not None else [])).copy()
178
+
179
+ df['u'] = df[WS].to_numpy() * np.sin(np.radians(df[WD].to_numpy()))
180
+ df['v'] = df[WS].to_numpy() * np.cos(np.radians(df[WD].to_numpy()))
181
+
182
+ u_bins = np.linspace(df.u.min(), df.u.max(), resolution)
183
+ v_bins = np.linspace(df.v.min(), df.v.max(), resolution)
184
+
185
+ # 使用 u_group 和 v_group 進行分組
186
+ df['u_group'] = pd.cut(df['u'], u_bins)
187
+ df['v_group'] = pd.cut(df['v'], v_bins)
188
+ grouped = df.groupby(['u_group', 'v_group'], observed=False)
189
+
190
+ X, Y = np.meshgrid(u_bins, v_bins)
191
+
192
+ # Note:
193
+ # The CBPF is the ratio between the number of points in each cell and the total number of points.
194
+ # So, it is not equal to the probability density function (PDF) of the wind speed and wind direction.
195
+
196
+ if percentile is None:
197
+ histogram = (grouped[val].count() / grouped[val].count().sum()).unstack().values.T
198
+ # histogram, v_edges, u_edges = np.histogram2d(df.v, df.u, bins=(v_bins, u_bins))
199
+ # histogram = histogram / histogram.sum()
200
+ histogram = np.where(histogram == 0, np.nan, histogram)
201
+ bottom_text = rf'$PDF\ plot$'
202
+
203
+ else:
204
+ if not all(0 <= p <= 100 for p in (percentile if isinstance(percentile, list) else [percentile])):
205
+ raise ValueError("Percentile must be between 0 and 100")
206
+
207
+ if isinstance(percentile, (float, int)):
208
+ bottom_text = rf'$CPF:\ >{int(percentile)}^{{th}}$'
209
+ thershold = df[val].quantile(percentile / 100)
210
+ cond = lambda x: (x >= thershold).sum()
211
+
212
+ elif isinstance(percentile, list) and len(percentile) == 1:
213
+ # Extract the single element from the list
214
+ single_percentile = percentile[0]
215
+ bottom_text = rf'$CPF:\ >{int(single_percentile)}^{{th}}$'
216
+ threshold = df[val].quantile(single_percentile / 100)
217
+ cond = lambda x: (x >= threshold).sum()
218
+
219
+ else:
220
+ bottom_text = rf'$CPF:\ {int(percentile[0])}^{{th}}\ to\ {int(percentile[1])}^{{th}}$'
221
+ thershold_small, thershold_large = df[val].quantile([percentile[0] / 100, percentile[1] / 100])
222
+ cond = lambda x: ((x >= thershold_small) & (x < thershold_large)).sum()
223
+
224
+ histogram = (grouped[val].apply(cond) / grouped[val].count()).unstack().values.T
225
+
226
+ # if np.isnan(histogram).all():
227
+ # raise "CBPF_array contains only NaN values."
228
+ # else:
229
+ # print(f"\nHistogram contains NaN before masking: {np.isnan(histogram).sum()}")
230
+
231
+ histogram_filled = np.nan_to_num(histogram, nan=0) # 將 NaN 替換為 0
232
+
233
+ filtered_histogram = gaussian_filter(histogram_filled, sigma=sigma)
234
+ # filtered_histogram[np.isnan(histogram)] = np.nan
235
+ # breakpoint()
236
+ # filtered_histogram = smooth_and_clean(filtered_histogram)
237
+
238
+ # Apply the function to your data
239
+ # fil_radius, magic_num = 3, 13
240
+ # filtered_histogram = remove_lonely_point(filtered_histogram, fil_radius, magic_num)
241
+ # filtered_histogram = fill_nan_with_mean(filtered_histogram, fil_radius, magic_num)
242
+
243
+ if np.all(np.isnan(filtered_histogram)):
244
+ raise ValueError("All values in the filtered histogram are NaN. Please decrease the resolution.")
245
+
246
+ # plot
247
+ fig, ax = plt.subplots()
248
+ fig.subplots_adjust(left=0)
249
+
250
+ surf = ax.pcolormesh(X, Y, filtered_histogram, shading='auto', cmap='jet', antialiased=True)
251
+
252
+ max_ws = max_ws or np.concatenate((abs(df.u), abs(df.v))).max() # Get the maximum value of the wind speed
253
+
254
+ radius_lst = np.arange(1, math.ceil(max_ws) + 1) # Create a list of radius
255
+
256
+ for i, radius in enumerate(radius_lst):
257
+ circle = plt.Circle((0, 0), radius, fill=False, color='gray', linewidth=1, linestyle='--', alpha=0.5)
258
+ ax.add_artist(circle)
259
+
260
+ for angle, label in zip(range(0, 360, 90), ["E", "N", "W", "S"]):
261
+ radian = np.radians(angle)
262
+ line_x, line_y = radius * np.cos(radian), radius * np.sin(radian)
263
+
264
+ if i + 2 == len(radius_lst): # Add wind direction line and direction label at the edge of the circle
265
+ ax.plot([0, line_x * 1.05], [0, line_y * 1.05], color='k', linestyle='-', linewidth=1, alpha=0.5)
266
+ ax.text(line_x * 1.15, line_y * 1.15, label, ha='center', va='center')
267
+
268
+ ax.text(radius * np.cos(np.radians(rlabel_pos)), radius * np.sin(np.radians(rlabel_pos)),
269
+ str(radius) + ' m/s', ha='center', va='center', fontsize=8)
270
+
271
+ for radius in range(math.ceil(max_ws) + 1, 10):
272
+ circle = plt.Circle((0, 0), radius, fill=False, color='gray', linewidth=1, linestyle='--', alpha=0.5)
273
+ ax.add_artist(circle)
274
+
275
+ ax.set(xlim=(-max_ws * 1.02, max_ws * 1.02),
276
+ ylim=(-max_ws * 1.02, max_ws * 1.02),
277
+ xticks=[],
278
+ yticks=[],
279
+ xticklabels=[],
280
+ yticklabels=[],
281
+ aspect='equal')
282
+
283
+ if bottom_text:
284
+ ax.text(0.50, -0.05, bottom_text, fontweight='bold', fontsize=8, va='center', ha='center',
285
+ transform=ax.transAxes)
286
+
287
+ ax.text(0.5, 1.05, Unit(val), fontweight='bold', fontsize=12, va='center', ha='center', transform=ax.transAxes)
288
+
289
+ cbar = plt.colorbar(surf, ax=ax, label='Frequency', pad=0.01, fraction=0.04)
290
+ cbar.ax.yaxis.label.set_fontsize(8)
291
+ cbar.ax.tick_params(labelsize=8)
292
+
293
+ plt.show()
294
+
295
+ return fig, ax
@@ -0,0 +1,3 @@
1
+ from .CBPF import CBPF
2
+ from .hysplit import hysplit
3
+ from .wind_rose import wind_rose
@@ -0,0 +1,93 @@
1
+ from pathlib import Path
2
+
3
+ import cartopy.crs as ccrs
4
+ import cartopy.feature as cfeature
5
+ import matplotlib.pyplot as plt
6
+ import pandas as pd
7
+
8
+ from AeroViz.plot.utils import set_figure
9
+
10
+ # Hybrid Single-Particle Lagrangian Integrated Trajectory (HYSPLIT) model
11
+
12
+
13
+ __all__ = ['hysplit']
14
+
15
+ # 設置默認文件路徑
16
+ DEFAULT_FILE = Path(__file__).parent.parent.parent / 'data' / 'hysplit_example_data.txt'
17
+
18
+
19
+ def read_hysplit_data(file: Path):
20
+ data = pd.read_csv(file, skiprows=8, sep=r'\s+', names=range(0, 12), engine='python')
21
+ data = data.reset_index(drop=False)
22
+ data.columns = ['category', 'name', 'year', 'month', 'day', 'hour', 'minute', 'count', 'backward', 'lat', 'lon',
23
+ 'height', 'pressure']
24
+
25
+ time_cols = ['year', 'month', 'day', 'hour', 'minute']
26
+
27
+ data['time'] = pd.to_datetime(data[time_cols].astype(str).agg(''.join, axis=1), format='%y%m%d%H%M')
28
+
29
+ data = data.drop(columns=time_cols)
30
+
31
+ data = data[['time'] + [col for col in data.columns if col != 'time']]
32
+
33
+ return data
34
+
35
+
36
+ @set_figure
37
+ def hysplit(file: Path = DEFAULT_FILE):
38
+ data = read_hysplit_data(file)
39
+
40
+ # 創建地圖
41
+ fig, ax = plt.subplots(figsize=(4, 5), subplot_kw={'projection': ccrs.PlateCarree()})
42
+
43
+ ax.set_global()
44
+ # ax.stock_img()
45
+
46
+ # 設置地圖範圍
47
+ ax.set_extent([116, 126, 17, 30], crs=ccrs.PlateCarree())
48
+
49
+ # 添加自然地理特徵
50
+ ax.add_feature(cfeature.LAND.with_scale('10m'))
51
+ ax.add_feature(cfeature.OCEAN.with_scale('10m'))
52
+ ax.add_feature(cfeature.COASTLINE.with_scale('10m'))
53
+ ax.add_feature(cfeature.BORDERS.with_scale('10m'), linestyle=':')
54
+
55
+ # 添加經緯度網格
56
+ ax.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
57
+
58
+ # 定義四種顏色
59
+ colors = ['red', 'blue', 'green', 'purple']
60
+
61
+ # 繪製四條軌跡線
62
+ group = data.groupby('category')
63
+ for i, (name, _data) in enumerate(group):
64
+ trajectory = _data
65
+ ax.plot(trajectory['lon'], trajectory['lat'], color=colors[i],
66
+ linewidth=2, transform=ccrs.Geodetic(),
67
+ label=f'Trajectory {name}')
68
+
69
+ # 添加起點和終點標記
70
+ # ax.plot(trajectory['lon'].iloc[-1], trajectory['lat'].iloc[-1], 'o',
71
+ # color=colors[i], markersize=4, transform=ccrs.Geodetic())
72
+ # ax.plot(trajectory['lon'].iloc[0], trajectory['lat'].iloc[0], 's',
73
+ # color=colors[i], markersize=4, transform=ccrs.Geodetic())
74
+
75
+ ax.legend(loc='upper right')
76
+ # 添加色標
77
+ # cbar = plt.colorbar(scatter, ax=ax, shrink=0.6, pad=0.12)
78
+ # cbar.set_label('Height (m)')
79
+
80
+ # 添加標題
81
+ plt.title("HYSPLIT model", pad=12)
82
+
83
+ plt.tight_layout()
84
+
85
+ # 保存地圖
86
+ plt.savefig('backward_hysplit.png', dpi=300, bbox_inches='tight')
87
+
88
+ # 顯示地圖(可選)
89
+ plt.show()
90
+
91
+
92
+ if __name__ == "__main__":
93
+ hysplit() # 請替換為您的實際檔案路徑
@@ -0,0 +1,77 @@
1
+ from typing import Literal
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ import windrose
6
+ from matplotlib.pyplot import Figure, Axes
7
+ from pandas import DataFrame, Series
8
+
9
+ from AeroViz.plot.utils import *
10
+
11
+ __all__ = ['wind_rose']
12
+
13
+
14
+ @set_figure(figsize=(4.3, 4))
15
+ def wind_rose(df: DataFrame,
16
+ WS: Series | str,
17
+ WD: Series | str,
18
+ val: Series | str | None = None,
19
+ typ: Literal['bar', 'scatter'] = 'scatter',
20
+ rlabel_pos: float = 30,
21
+ **kwargs
22
+ ) -> tuple[Figure, Axes]:
23
+ # conditional bivariate probability function (cbpf) python
24
+ # https://davidcarslaw.github.io/openair/reference/polarPlot.html
25
+ # https://github.com/davidcarslaw/openair/blob/master/R/polarPlot.R
26
+ windrose.WindroseAxes._info = 'WindroseAxes'
27
+
28
+ df = df.dropna(subset=[WS, WD] + ([val] if val is not None else []))
29
+
30
+ radius = df[WS].to_numpy()
31
+ theta = df[WD].to_numpy()
32
+ radian = np.radians(theta)
33
+ values = df[val].to_numpy() if val is not None else None
34
+
35
+ # In this case, the windrose is a simple frequency diagram,
36
+ # the function automatically calculates the radians of the given wind direction.
37
+ if typ == 'bar':
38
+ fig, ax = plt.subplots(figsize=(5.5, 4), subplot_kw={'projection': 'windrose'})
39
+ fig.subplots_adjust(left=0)
40
+
41
+ ax.bar(theta, radius, bins=[0, 1, 2, 3], normed=True, colors=['#0F1035', '#365486', '#7FC7D9', '#DCF2F1'])
42
+ ax.set(
43
+ ylim=(0, 30),
44
+ yticks=[0, 15, 30],
45
+ yticklabels=['', '15 %', '30 %'],
46
+ rlabel_position=rlabel_pos
47
+ )
48
+ ax.set_thetagrids(angles=[0, 45, 90, 135, 180, 225, 270, 315],
49
+ labels=["E", "NE", "N", "NW", "W", "SW", "S", "SE"])
50
+
51
+ ax.legend(units='m/s', bbox_to_anchor=[1.1, 0.5], loc='center left', ncol=1)
52
+
53
+ # In this case, the windrose is a scatter plot,
54
+ # in contrary, this function does not calculate the radians, so user have to input the radian.
55
+ else:
56
+ fig, ax = plt.subplots(figsize=(5, 4), subplot_kw={'projection': 'windrose'})
57
+ fig.subplots_adjust(left=0)
58
+
59
+ scatter = ax.scatter(radian, radius, s=15, c=values, vmax=np.quantile(values, 0.90), edgecolors='none',
60
+ cmap='jet', alpha=0.8)
61
+ ax.set(
62
+ ylim=(0, 7),
63
+ yticks=[1, 3, 5, 7],
64
+ yticklabels=['1 m/s', '3 m/s', '5 m/s', '7 m/s'],
65
+ rlabel_position=rlabel_pos,
66
+ theta_direction=-1,
67
+ theta_zero_location='N',
68
+ title=kwargs.get('title', None)
69
+ )
70
+ ax.set_thetagrids(angles=[0, 45, 90, 135, 180, 225, 270, 315],
71
+ labels=["N", "NE", "E", "SE", "S", "SW", "W", "NW"])
72
+
73
+ plt.colorbar(scatter, ax=ax, label=Unit(val), pad=0.1, fraction=0.04)
74
+
75
+ plt.show()
76
+
77
+ return fig, ax
@@ -0,0 +1 @@
1
+ from .optical import *