colindex2 2.10.5__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.
colindex2/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ from .colindex2 import Detect
2
+ from .commands import detect, track, gen_data_settings, find_track
3
+ from .tracking_overlap2 import Track
4
+ from .get_IDfile import Finder
5
+ from .draw_map import _draw_map, draw_ro
6
+
7
+ from .modules import gcd
8
+ from .modules import invert_gcd2
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+
6
+ def area_heron_x2(a, b, c):
7
+ s = (a+b+c)/2
8
+ if min(s-a, s-b, s-c) <= 0:
9
+ return np.nan
10
+ else:
11
+ return np.sqrt(s*(s-a)*(s-b)*(s-c))
12
+
13
+
14
+ class circle:
15
+ def __init__(self, r, x):
16
+ self.r = r
17
+ self.x = x
18
+ self.S = np.pi * r * r
19
+
20
+
21
+ class circle_overlap:
22
+ def __init__(self, c1, c2, d):
23
+
24
+ fct = 1
25
+ S_square = area_heron_x2(c1.r, c2.r, d)
26
+
27
+ if np.isnan(S_square): # cannot make inner triangle
28
+
29
+ alpha1 = np.nan
30
+ d1 = np.nan
31
+ h = np.nan
32
+
33
+ if d < c1.r:
34
+ overlap = 1.
35
+ else:
36
+ overlap = 0.
37
+
38
+ else: # can make inner triangle
39
+ # https://tjkendev.github.io/procon-library/python/geometry/circles_intersection_area.html
40
+
41
+ theta1 = np.arccos((c1.r**2 + d**2 - c2.r**2) / (2 * c1.r * d))
42
+ theta2 = np.arccos((c2.r**2 + d**2 - c1.r**2) / (2 * c2.r * d))
43
+
44
+ S_fan = c1.r**2 * theta1 + c2.r**2 * theta2
45
+ S_and = S_fan - S_square
46
+
47
+ # complete overlap ratio
48
+ S_or = c1.S + c2.S - S_and
49
+ overlap = min(S_and / S_or, 1.0)
50
+
51
+ # smaller-side overlap ratio
52
+ #S_small = min(c1.S, c2.S)
53
+ #overlap = min(S_and / S_small, 1.0)
54
+
55
+ self.overlap = overlap
56
+
57
+
58
+ def test():
59
+
60
+ import matplotlib.pyplot as plt
61
+ import matplotlib.patches as patches
62
+
63
+ r1, r2 = 500, 600
64
+
65
+ ds = np.arange(100, 1151, 50)
66
+ overs = []
67
+ alpha1s = []
68
+ d1s = []
69
+ hs = []
70
+
71
+ for d in ds:
72
+
73
+ c1 = circle(r=r1, x=0)
74
+ c2 = circle(r=r2, x=d)
75
+ o = cirlce_overlap(c1, c2, d)
76
+ overs.append(o.overlap)
77
+ alpha1s.append(o.alpha1)
78
+ d1s.append(o.d1)
79
+ hs.append(o.h)
80
+
81
+
82
+ for i, d in enumerate(ds):
83
+
84
+ print(f'{d=}')
85
+
86
+ ax = plt.axes()
87
+ C1 = patches.Circle(xy=(0, 500), radius=r1, fc='none', ec='tab:blue')
88
+ C2 = patches.Circle(xy=(d, 500), radius=r2, fc='none', ec='tab:blue')
89
+ ax.add_patch(C1)
90
+ ax.add_patch(C2)
91
+ ax.plot(0, 500, marker='.', c='tab:blue')
92
+ ax.plot(d, 500, marker='.', c='tab:blue')
93
+ ax.plot([d1s[i], d1s[i]], [500-hs[i], 500+hs[i]], c='r')
94
+ ax.set_aspect('equal')
95
+ ax.set_xlim([-700,1250])
96
+ ax.set_ylim([-200, 1200])
97
+ ax.set_title(f'O={overs[i]:.3f}')
98
+ plt.savefig(f'fig/cc_d{d}.png')
99
+ plt.close()
100
+
101
+ ax = plt.axes()
102
+ ax.plot(ds, overs)
103
+ ax.plot(ds[i], overs[i], '.r')
104
+ plt.savefig(f'fig/overs_{d}.png')
105
+ plt.close()
106
+
107
+ '''ax = plt.axes()
108
+ ax.plot(ds, np.array(alpha1s)/np.pi)
109
+ ax.plot(ds[i], alpha1s[i]/np.pi, '.r')
110
+ plt.savefig(f'fig/alpha1s_d{d}.png')
111
+ plt.close()'''
112
+
113
+
114
+ if __name__ == '__main__':
115
+ test()
colindex2/colindex2.py ADDED
@@ -0,0 +1,319 @@
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ import xarray as xr
5
+ #import multiprocessing as mp
6
+ import warnings
7
+ warnings.filterwarnings('ignore')
8
+
9
+ from .modules import check_gpu
10
+ from .modules import _calc_as_torch
11
+
12
+ class Detect():
13
+ """
14
+ Depression detector by searching each optimal location, intensity, and size from 2D fields.
15
+ See puplication for details (Kasuga et al. 2021; K21) and (Kasuga and Honda 2025; KH25).
16
+
17
+ ----
18
+
19
+ Parameters:
20
+ -----------
21
+
22
+ da : xarray.DataArray or numpy.ndarray
23
+ The target 2D field with (or without) time axis.
24
+ The dimmension order must be time->latitude->longitude.
25
+
26
+ odir : str, default './d01'
27
+ Parent output directory path. By defaults, output data will be stored as follows
28
+ Outputs will be produced in {odir}/AS (AS field) and {odir}/V (detected point values).
29
+
30
+ ty : str, {'both', 'L', 'H'}, default 'both'
31
+ Type of vortices to be detected.
32
+
33
+ r : np.array, default np.arange(200, 2101, 100) (K21)
34
+ Radial searching variable for average slop function (AS) [km].
35
+ Set range to include scales of which phenomena you are interested in.
36
+
37
+ SR_thres : float, default 3. (K21)
38
+ Threshold to remove noises with respect to the slope ratio.
39
+
40
+ So_thres : float, default 3. (KH25)
41
+ Threshold to remove noises with respect to the intensity [m/100km].
42
+ In K21, ``10`` and ``5`` was used for climatology for cutoff lows and preexisting troughs, respectively.
43
+
44
+ Do_thres : float, default 0.
45
+ Threshold to remove noises with respect to the depth [m].
46
+ A large value (e.g., 10.) reduces small-size noise, which might be helpful for surface low/high detection.
47
+
48
+ xx_thres : float, default 0.5
49
+ Threshold to remove noises with respect to the zonal laplacan [m/(100 km)^2].
50
+ This works for high detection
51
+ by removeing large and weak ridges located south of the subtropical jet.
52
+
53
+ rm_rmin : bool, default True
54
+ Detected point will be removed if ro is minimum of r because this may be too small.
55
+ True in K21 and KH25.
56
+
57
+ rm_rmax : bool, default False
58
+ Detected point will be removed if ro is maximum of r because this may be too large.
59
+ True in K21 and False in KH25.
60
+
61
+ factor : float, default 100.
62
+ Factor to convert units of So and AS. Default convert m/km -> m/100 km for geopotential height input.
63
+
64
+ local_ex_req_num : int, default 7.
65
+ Number required in local extremum condition (N).
66
+ The conditions of minimum/maximum are defined in a 3x3 grid box as at least N points (out of 8 points)
67
+ must greater/smaller than and equal to the center value.
68
+
69
+ gpu : bool, default False
70
+ Note, this feature is experimental.
71
+ If True, the processes wil be computed with gpu processing via CUDA or METAL.
72
+
73
+ nc : bool, default True
74
+ If True, netcdf file will be created for AS. If you want grads binary outputs, set False.
75
+
76
+ fmt : str, default '<f4'
77
+ Output binary format when nc=False. The format is correspond to dtype in numpy.
78
+
79
+ subs : bool, default False
80
+ If True, 2D fields of ro, m, and n are produced.
81
+
82
+ t : pandas.DatetimeIndex, default [pd.Timestamp('1000-01-01 00:00')]
83
+ Time of data. Option for numpy.ndarray input.
84
+
85
+ lev : int, default 0
86
+ Level of data. Option for numpy.ndarray input. This will be used in the name of output files.
87
+ Note: Float level will be converted to integer.
88
+
89
+ lons : list or numpy.ndarray, default [-999.]
90
+ Longitude list. Option for numpy.ndarray input. Set like lon=np.arange(0, 360, 1.25).
91
+
92
+
93
+ lats : list or numpy.ndarray, default [-999.]
94
+ Option for numpy.ndarray input. Set like lon=np.arange(-90, 90, 1.25).
95
+
96
+ """
97
+
98
+ def __init__(self,
99
+ da, # 2D DataArray or ndarray
100
+ odir='./d01', # output parent dir
101
+ ty="both",
102
+ r=np.arange(200, 2101, 100), # searching radius variable
103
+ SR_thres=3., # shape threshold
104
+ So_thres=3., # intensity threshold
105
+ Do_thres=0., # depth threshold
106
+ xx_thres=.5, # zonal concavity thershold
107
+ rm_rmin=True, # remove minimum size
108
+ rm_rmax=False, # remove maximum size
109
+ factor=100., # factor of So m/km -> m/100km
110
+ local_ex_req_num=7, # out of 8 points
111
+ gpu=False, # gpu calculation
112
+ nc=True, # netcdf outputs otherwise grads bin
113
+ fmt='<f4', # binary format when nc=False
114
+ subs=False, # output intermediate fields
115
+ lev=0, # for ndarray input below
116
+ t=[pd.Timestamp('1700-01-01 00:00')],
117
+ lons=[-999.],
118
+ lats=[-999.],
119
+ ):
120
+
121
+ # constants
122
+ self.rr = 6371. # earth radius [km]
123
+ self.g = 9.8 # gravity [m/ss]
124
+ self.factor = factor
125
+
126
+ self.ty = ty
127
+ self.r = np.array(r)
128
+
129
+ self.da = da
130
+ self.odir = odir
131
+ self.SR_thres = SR_thres
132
+ self.So_thres = So_thres
133
+ self.Do_thres = Do_thres
134
+ self.xx_thres = xx_thres
135
+ self.thres4 = [So_thres, Do_thres, SR_thres, xx_thres]
136
+ self.rm_rmin = rm_rmin
137
+ self.rm_rmax = rm_rmax
138
+ self.local_ex_req_num = local_ex_req_num
139
+
140
+ self.lons = lons
141
+ self.lats = lats
142
+ self.t = t
143
+ self.lev = lev
144
+
145
+ self.gpu = gpu
146
+ self.nc = nc
147
+ self.fmt = fmt
148
+ self.subs = subs
149
+
150
+ # main process
151
+ self._main()
152
+
153
+ # maxe a text about detection parameters
154
+ self._save_param_text()
155
+
156
+ print("done!")
157
+
158
+
159
+ def _set_coords(self):
160
+
161
+ da = self.da
162
+ lons = self.lons
163
+ lats = self.lats
164
+ lev = self.lev
165
+ t = self.t
166
+ if isinstance(da, xr.DataArray):
167
+
168
+ _dims = [k for k, v in da.coords.items()] # this may include squeezed dims
169
+
170
+ if 'valid_time' in _dims:
171
+ tt = pd.to_datetime(da.valid_time.values)
172
+ elif 'time' in _dims:
173
+ tt = pd.to_datetime(da.time.values)
174
+
175
+ if isinstance(tt, pd.Timestamp):
176
+ self.t = [tt] # must be iterable
177
+ self.da = da.values[np.newaxis, ...]
178
+ else:
179
+ self.t = tt # pd.DatetimeIndex or default list
180
+ self.da = da.values
181
+
182
+ if 'level' in _dims:
183
+ self.lev = da.level.values
184
+ elif 'lev' in _dims:
185
+ self.lev = da.lev.values
186
+ elif 'levs' in _dims:
187
+ self.lev = da.levs.values
188
+ elif 'isobaricInhPa' in _dims:
189
+ self.lev = da.isobaricInhPa.values
190
+
191
+ if 'longitude' in _dims:
192
+ self.lon = da.longitude.values
193
+ if 'lon' in _dims:
194
+ self.lon = da.lon.values
195
+ if 'lons' in _dims:
196
+ self.lon = da.lons.values
197
+
198
+ if 'latitude' in _dims:
199
+ self.lat = da.latitude.values
200
+ if 'lat' in _dims:
201
+ self.lat = da.lat.values
202
+ if 'lats' in _dims:
203
+ self.lat = da.lats.values
204
+
205
+ elif isinstance(da, np.ndarray):
206
+
207
+ if len(lons) != da.shape[-1] or len(lats) != da.shape[-2]:
208
+ raise ValueError('shape of lons or lats not fit input')
209
+ else:
210
+ self.t = t
211
+ self.lev = lev
212
+ self.lon = lons
213
+ self.lat = lats
214
+ self.da = da
215
+
216
+ if len(self.t) == 1:
217
+ self.da = [self.da]
218
+ self.t = [self.t]
219
+
220
+ else:
221
+ raise ValueError('wrong input data type, must be xr.DataArray or np.ndarray')
222
+
223
+ if isinstance(self.lev, np.ndarray):
224
+ self.lev = self.lev.item()
225
+
226
+ self.lev = int(self.lev)
227
+
228
+
229
+
230
+ def check_zonal_loop(self):
231
+
232
+ lon1 = self.lon[1]
233
+ lon1n = self.lon[-1]
234
+ lon0 = self.lon[0]
235
+ dlon = lon1 - lon0
236
+
237
+ if lon1n == lon0:
238
+ raise ValueError("longitude is already looped. use non-looped data")
239
+
240
+ l = lon1n + dlon
241
+
242
+ if l == lon0 or l+360 == lon0 or l-360 == lon0:
243
+ self.zloop = True
244
+ else:
245
+ self.zloop = False
246
+
247
+ def check_increasing_lat(self):
248
+
249
+ dlat = self.lat[1] - self.lat[0]
250
+
251
+ if dlat < 0.:
252
+ self.da = self.da[...,::-1,:].copy()
253
+ self.lat = self.lat[::-1].copy()
254
+
255
+
256
+ def _main(self):
257
+
258
+ # set coordinates of data
259
+ self._set_coords()
260
+
261
+ # check gpu is available
262
+ self.device = check_gpu(self.gpu)
263
+
264
+ # check whether data longitude is looped
265
+ # and increasing latitude
266
+ self.check_zonal_loop()
267
+ self.check_increasing_lat()
268
+
269
+ print("level:", self.lev)
270
+ print("calculation:", self.device)
271
+ print("-----------------------")
272
+
273
+ # start main process
274
+ args = [(
275
+ self.odir,
276
+ self.ty,
277
+ self.da[i],
278
+ self.lon,
279
+ self.lat,
280
+ self.lev,
281
+ self.t[i],
282
+ self.r,
283
+ self.thres4,
284
+ self.rm_rmin,
285
+ self.rm_rmax,
286
+ self.factor,
287
+ self.zloop,
288
+ self.device,
289
+ self.nc,
290
+ self.fmt,
291
+ self.subs,
292
+ self.local_ex_req_num,
293
+ ) for i in range(len(self.t))]
294
+
295
+ #with mp.Pool(self.nproc) as p:
296
+ # p.map(_calc_as_torch, args)
297
+ for x in args:
298
+ _calc_as_torch(x)
299
+
300
+ def _save_param_text(self):
301
+ from datetime import datetime
302
+ import getpass
303
+ import socket
304
+ now = datetime.now()
305
+ user = getpass.getuser()
306
+ host = socket.gethostname()
307
+ with open(f"{self.odir}/_detection_parms.txt", "w", encoding="utf-8") as f:
308
+ print("r:", self.r, file=f)
309
+ print("SR_thres:", self.SR_thres, file=f)
310
+ print("So_thres:", self.So_thres, file=f)
311
+ print("Do_thres:", self.Do_thres, file=f)
312
+ print("xx_thres:", self.xx_thres, file=f)
313
+ print("rm_rmin:", self.rm_rmin, file=f)
314
+ print("rm_rmax:", self.rm_rmax, file=f)
315
+ print("factor:", self.factor, file=f)
316
+ print("local_ex_req_num:", self.local_ex_req_num, file=f)
317
+ print("calculation device:", self.device, file=f)
318
+ print("", file=f)
319
+ print("executed in", now, "by", user, "@", host, file=f)
colindex2/commands.py ADDED
@@ -0,0 +1,269 @@
1
+ import sys, os
2
+ import argparse
3
+ import importlib.util
4
+ import numpy as np
5
+ import pandas as pd
6
+ import xarray as xr
7
+ from .colindex2 import Detect
8
+ from .tracking_overlap2 import Track
9
+ from .get_IDfile import Finder
10
+ import shutil
11
+ from pathlib import Path
12
+ import importlib.resources
13
+
14
+ def gen_data_settings():
15
+ with importlib.resources.path("colindex2", "data_settings.py") as file_path:
16
+ dest = Path.cwd() / "data_settings.py"
17
+ shutil.copy(file_path, dest)
18
+
19
+ def _load_local_module(path):
20
+ if not os.path.exists(path):
21
+ raise FileNotFoundError(f"{path} has not generated yet. Use gen_data_settings command")
22
+ spec = importlib.util.spec_from_file_location("data_settings", path)
23
+ module = importlib.util.module_from_spec(spec)
24
+ sys.modules["data_settings"] = module
25
+ spec.loader.exec_module(module)
26
+ return module
27
+
28
+
29
+ def detect():
30
+ description = """
31
+ Run detection of colindex.
32
+ Before run, generate data_settings.py with 'gen_data_settings' command and edit it.
33
+
34
+ $ gen_data_settings
35
+
36
+ Then execute below for netcdf input.
37
+
38
+ $ detect path/to/file.nc
39
+
40
+ For binary input, specify start/end times (st,et) and frequency of timesteps (freq).
41
+
42
+ $ detect path/to/file.grd -st "yyyy-mm-dd hh" -et "yyyy-mm-dd hh" -freq 6h
43
+
44
+ Option -track execute also tracking after detection.
45
+ In this case, 'tracking_times' in data_settings.py is ignored,
46
+ and the detection timesteps will be used.
47
+
48
+ $ detect path/to/file.nc -track
49
+ $ detect path/to/file.grd -st "yyyy-mm-dd hh" -et "yyyy-mm-dd hh" -freq 6h -track
50
+
51
+ Result data will be saved in output_dir/V output_dir/AS.
52
+ """
53
+ #Option -n can set number of multiprocess (default 4)
54
+ #
55
+ # $ detect path/to/file.nc -n 20
56
+
57
+ parser = argparse.ArgumentParser(description=description,
58
+ formatter_class=argparse.RawTextHelpFormatter)
59
+ parser.add_argument("input_data_path",
60
+ type=str,
61
+ help="input data path")
62
+ #parser.add_argument("-n",
63
+ # type=int,
64
+ # help="number of multiprocessing, default is 4.",
65
+ # default=4)
66
+ parser.add_argument("-st",
67
+ type=str,
68
+ help="start timestep yyyy-mm-dd hh",
69
+ default="1700-01-01 00")
70
+ parser.add_argument("-et",
71
+ type=str,
72
+ help="end timestep yyyy-mm-dd hh",
73
+ default="1700-01-01 00")
74
+ parser.add_argument("-freq",
75
+ type=str,
76
+ help="frequency of timestep for pd.date_range (default: '6h')",
77
+ default="6h")
78
+ parser.add_argument("-track",
79
+ action="store_true",
80
+ help="enable tracking",)
81
+ args = parser.parse_args()
82
+ input_data_path = args.input_data_path
83
+ st = args.st
84
+ et = args.et
85
+ freq = args.freq
86
+ _track = args.track
87
+
88
+ data_settings_path = os.path.join(os.getcwd(), "data_settings.py")
89
+ if not os.path.exists(data_settings_path):
90
+ raise FileNotFoundError("No data_settings.py in the current dir. Run 'gen_data_settings' command.")
91
+ D = _load_local_module(data_settings_path)
92
+
93
+ if D.input_data_type == "nc":
94
+ if not hasattr(D, "var_name"):
95
+ da = xr.open_dataarray(input_data_path)
96
+ else:
97
+ ds = xr.open_dataset(input_data_path)
98
+ da = ds[D.var_name]
99
+
100
+ if hasattr(D, "lon_name"):
101
+ da = da.rename({D.lon_name:"longitude"})
102
+ if hasattr(D, "lat_name"):
103
+ da = da.rename({D.lat_name:"latitude"})
104
+ if hasattr(D, "lev_name"):
105
+ da = da.rename({D.lev_name:"level"})
106
+ if hasattr(D, "time_name"):
107
+ da = da.rename({D.time_name:"time"})
108
+
109
+ lons = da.longitude.values
110
+ lats = da.latitude.values
111
+ levs = da.level.values
112
+ times = pd.to_datetime(da.time.values)
113
+ da = da.values
114
+ fmt = None
115
+
116
+ else:
117
+ lons = D.lons
118
+ lats = D.lats
119
+ levs = D.levs
120
+ #times = D.times
121
+ times = pd.date_range(st,et,freq=freq)
122
+ shape = (len(times),len(levs),len(lats),len(lons))
123
+ fmt = D.input_data_type
124
+ with open(input_data_path, "r") as a:
125
+ da = np.fromfile(a, dtype=fmt).reshape(shape)
126
+
127
+ if D.output_data_type == "nc":
128
+ nc = True
129
+ else:
130
+ nc = False
131
+ fmt = D.output_data_type
132
+
133
+ idx_levs = np.where(np.isin(levs, D.selected_levs))[0]
134
+
135
+ for l in idx_levs:
136
+ Detect(da[:,l,:,:],
137
+ D.output_dir,
138
+ D.detection_type,
139
+ D.r,
140
+ D.SR_thres,
141
+ D.So_thres,
142
+ D.Do_thres,
143
+ D.xx_thres,
144
+ D.rm_rmin,
145
+ D.rm_rmax,
146
+ D.factor,
147
+ D.local_ex_req_num,
148
+ D.gpu,
149
+ nc,
150
+ fmt,
151
+ D.subs,
152
+ levs[l],
153
+ times,
154
+ lons,
155
+ lats,
156
+ )
157
+
158
+ if _track:
159
+ track_cli(times)
160
+
161
+
162
+ def track_cli(times=0):
163
+
164
+ data_settings_path = os.path.join(os.getcwd(), "data_settings.py")
165
+ if not os.path.exists(data_settings_path):
166
+ raise FileNotFoundError("No data_settings.pn in the current dir. Run 'gen_data_settings' command.")
167
+ D = _load_local_module(data_settings_path)
168
+
169
+ if isinstance(times, pd.DatetimeIndex):
170
+ tracking_times = times
171
+ else:
172
+ tracking_times = D.tracking_times
173
+
174
+ argss=[(
175
+ D.output_dir,
176
+ D.tracking_types,
177
+ lev,
178
+ tracking_times,
179
+ D.tlimit,
180
+ D.penalty,
181
+ D.long_term,
182
+ D.operational,
183
+ D.DU_thres,
184
+ D.QS_min_intensity,
185
+ D.QS_min_radius,
186
+ D.QS_min_overlap_ratio,
187
+ D.only_count
188
+ ) for lev in D.selected_levs]
189
+
190
+ for _args in argss:
191
+ Track(*_args)
192
+
193
+
194
+ def track():
195
+ description = """
196
+ Run tracking of colindex. Before run, edit TRACKING SETTINGS section in data_settings.py
197
+
198
+ $ track
199
+
200
+ Result data will be saved in output_dir/Vtc.
201
+ """
202
+ parser = argparse.ArgumentParser(description=description,
203
+ formatter_class=argparse.RawTextHelpFormatter)
204
+ parser.parse_args()
205
+ track_cli()
206
+
207
+
208
+ def find_track():
209
+ description = """
210
+ Search tracking for specific IDs and save. Four arguments are required.
211
+ $ find_track output_dir type lev freqH type2
212
+
213
+ The fourth argument 'type2' can be set 3 types:
214
+
215
+ c: only count number of all tracks
216
+ $ find_track ./d01 L 300 6 c
217
+
218
+ A: find all tracks and save them as an all-included csv output_dir/all_{type}_{lev}.csv.
219
+ $ find_track ./d01 L 300 6 A
220
+
221
+ a: find all tracks and save them separately in output_dir/ID/.
222
+ $ find_track ./d01 L 300 6 a
223
+
224
+ (digit of ID): find one track labeled by ID. Add fifth argument of 'yyyymm' (year month) when the track observed, as following
225
+ $ find_track ./d01 L 300 6 2222 202302
226
+
227
+ This will produce a track obtained from 6-hour intarval data for 300-hPa level whose ID is 2222, appears in Feb 2023.
228
+ If add -a (-b) option at its tail, the tracks after merging (before splitting) are connected.
229
+ $ find_track ./d01 L 300 6 2222 202302 -a
230
+ $ find_track ./d01 L 300 6 2222 202302 -b
231
+ $ find_track ./d01 L 300 6 2222 202302 -a -b
232
+
233
+ """
234
+ parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter)
235
+ parser.add_argument("odir", type=str, help="output data directory")
236
+ parser.add_argument("ty", type=str, help="L or H")
237
+ parser.add_argument("lev", type=str, help="level")
238
+ parser.add_argument("freqH", type=int, help="frequency of timestep in hour")
239
+ parser.add_argument("type2", nargs="+", type=str, help="A or a or c or 'ID yyyymm'")
240
+ parser.add_argument('-a', '--option_a', action='store_true', default=False, help='follow track after merge')
241
+ parser.add_argument('-b', '--option_b', action='store_true', default=False, help='follow track before split')
242
+ args = parser.parse_args()
243
+ odir = args.odir
244
+ ty = args.ty
245
+ #lev = int(args.lev)
246
+ lev = args.lev
247
+ type2 = args.type2
248
+
249
+ #data_settings_path = os.path.join(os.getcwd(), "data_settings.py")
250
+ #D = _load_local_module(data_settings_path)
251
+ #freqH = int(D.tracking_times.inferred_freq[:-1])
252
+ freqH = args.freqH
253
+
254
+ f = Finder(odir, timestep=freqH)
255
+
256
+ if type2[0] == "a":
257
+ f.find_all(ty,lev)
258
+ elif type2[0] == "c":
259
+ f.count_all(ty,lev)
260
+ elif type2[0] == "A":
261
+ f.find_all(ty,lev,all_in_one=True)
262
+ elif args.option_a and args.option_b:
263
+ f.find_one(ty,lev,int(type2[1]), int(type2[0]),after_merge=True,before_split=True)
264
+ elif args.option_a:
265
+ f.find_one(ty,lev,int(type2[1]), int(type2[0]),after_merge=True)
266
+ elif args.option_b:
267
+ f.find_one(ty,lev,int(type2[1]), int(type2[0]),before_split=True)
268
+ else:
269
+ f.find_one(ty,lev,int(type2[1]), int(type2[0]))