tfv-get-tools 0.2.0__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.
- tfv_get_tools/__init__.py +4 -0
- tfv_get_tools/_standard_attrs.py +107 -0
- tfv_get_tools/atmos.py +167 -0
- tfv_get_tools/cli/_cli_base.py +173 -0
- tfv_get_tools/cli/atmos_cli.py +192 -0
- tfv_get_tools/cli/ocean_cli.py +204 -0
- tfv_get_tools/cli/tide_cli.py +118 -0
- tfv_get_tools/cli/wave_cli.py +183 -0
- tfv_get_tools/fvc/__init__.py +3 -0
- tfv_get_tools/fvc/_atmos.py +230 -0
- tfv_get_tools/fvc/_fvc.py +218 -0
- tfv_get_tools/fvc/_ocean.py +171 -0
- tfv_get_tools/fvc/_tide.py +195 -0
- tfv_get_tools/ocean.py +170 -0
- tfv_get_tools/providers/__init__.py +0 -0
- tfv_get_tools/providers/_custom_conversions.py +34 -0
- tfv_get_tools/providers/_downloader.py +566 -0
- tfv_get_tools/providers/_merger.py +520 -0
- tfv_get_tools/providers/_utilities.py +255 -0
- tfv_get_tools/providers/atmos/barra2.py +209 -0
- tfv_get_tools/providers/atmos/cfgs/barra2_c2.yaml +52 -0
- tfv_get_tools/providers/atmos/cfgs/barra2_r2.yaml +85 -0
- tfv_get_tools/providers/atmos/cfgs/barra2_re2.yaml +70 -0
- tfv_get_tools/providers/atmos/cfgs/cfsr.yaml +68 -0
- tfv_get_tools/providers/atmos/cfgs/era5.yaml +77 -0
- tfv_get_tools/providers/atmos/cfgs/era5_gcp.yaml +77 -0
- tfv_get_tools/providers/atmos/cfsr.py +207 -0
- tfv_get_tools/providers/atmos/era5.py +20 -0
- tfv_get_tools/providers/atmos/era5_gcp.py +20 -0
- tfv_get_tools/providers/ocean/cfgs/copernicus_blk.yaml +64 -0
- tfv_get_tools/providers/ocean/cfgs/copernicus_glo.yaml +67 -0
- tfv_get_tools/providers/ocean/cfgs/copernicus_nws.yaml +62 -0
- tfv_get_tools/providers/ocean/cfgs/hycom.yaml +73 -0
- tfv_get_tools/providers/ocean/copernicus_ocean.py +457 -0
- tfv_get_tools/providers/ocean/hycom.py +611 -0
- tfv_get_tools/providers/wave/cawcr.py +166 -0
- tfv_get_tools/providers/wave/cfgs/cawcr_aus_10m.yaml +39 -0
- tfv_get_tools/providers/wave/cfgs/cawcr_aus_4m.yaml +39 -0
- tfv_get_tools/providers/wave/cfgs/cawcr_glob_24m.yaml +39 -0
- tfv_get_tools/providers/wave/cfgs/cawcr_pac_10m.yaml +39 -0
- tfv_get_tools/providers/wave/cfgs/cawcr_pac_4m.yaml +39 -0
- tfv_get_tools/providers/wave/cfgs/copernicus_glo.yaml +56 -0
- tfv_get_tools/providers/wave/cfgs/copernicus_nws.yaml +51 -0
- tfv_get_tools/providers/wave/cfgs/era5.yaml +48 -0
- tfv_get_tools/providers/wave/cfgs/era5_gcp.yaml +48 -0
- tfv_get_tools/providers/wave/copernicus_wave.py +38 -0
- tfv_get_tools/providers/wave/era5.py +232 -0
- tfv_get_tools/providers/wave/era5_gcp.py +169 -0
- tfv_get_tools/tide/__init__.py +2 -0
- tfv_get_tools/tide/_nodestring.py +214 -0
- tfv_get_tools/tide/_tidal_base.py +568 -0
- tfv_get_tools/utilities/_tfv_bc.py +78 -0
- tfv_get_tools/utilities/horizontal_padding.py +89 -0
- tfv_get_tools/utilities/land_masking.py +93 -0
- tfv_get_tools/utilities/parsers.py +44 -0
- tfv_get_tools/utilities/warnings.py +38 -0
- tfv_get_tools/wave.py +179 -0
- tfv_get_tools-0.2.0.dist-info/METADATA +286 -0
- tfv_get_tools-0.2.0.dist-info/RECORD +62 -0
- tfv_get_tools-0.2.0.dist-info/WHEEL +5 -0
- tfv_get_tools-0.2.0.dist-info/entry_points.txt +5 -0
- tfv_get_tools-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Set of standard variable attributes for the merged dataset
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
STDVARS = {
|
|
6
|
+
"u10": {
|
|
7
|
+
"long_name": "10m_eastward_wind",
|
|
8
|
+
"standard_name": "eastward_wind",
|
|
9
|
+
"units": "m s-1"
|
|
10
|
+
},
|
|
11
|
+
"v10": {
|
|
12
|
+
"long_name": "10m_northward_wind",
|
|
13
|
+
"standard_name": "northward_wind",
|
|
14
|
+
"units": "m s-1"
|
|
15
|
+
},
|
|
16
|
+
"mslp": {
|
|
17
|
+
"long_name": "mean_sea_level_pressure",
|
|
18
|
+
"standard_name": "air_pressure_at_sea_level",
|
|
19
|
+
"units": "Pa"
|
|
20
|
+
},
|
|
21
|
+
"dlwrf": {
|
|
22
|
+
"long_name": "surface_downward_longwave_flux",
|
|
23
|
+
"standard_name": "surface_downwelling_longwave_flux_in_air",
|
|
24
|
+
"units": "W m-2"
|
|
25
|
+
},
|
|
26
|
+
"dswrf": {
|
|
27
|
+
"long_name": "surface_downward_shortwave_flux",
|
|
28
|
+
"standard_name": "surface_downwelling_shortwave_flux_in_air",
|
|
29
|
+
"units": "W m-2"
|
|
30
|
+
},
|
|
31
|
+
"t2m": {
|
|
32
|
+
"long_name": "2m_air_temperature",
|
|
33
|
+
"standard_name": "air_temperature",
|
|
34
|
+
"units": "K"
|
|
35
|
+
},
|
|
36
|
+
"prate": {
|
|
37
|
+
"long_name": "precipitation_rate",
|
|
38
|
+
"standard_name": "precipitation_flux",
|
|
39
|
+
"units": "kg m-2 s-1"
|
|
40
|
+
},
|
|
41
|
+
"relhum": {
|
|
42
|
+
"long_name": "relative_humidity",
|
|
43
|
+
"standard_name": "relative_humidity",
|
|
44
|
+
"units": "1"
|
|
45
|
+
},
|
|
46
|
+
"water_u": {
|
|
47
|
+
"long_name": "eastward_sea_water_velocity",
|
|
48
|
+
"standard_name": "eastward_sea_water_velocity",
|
|
49
|
+
"units": "m s-1"
|
|
50
|
+
},
|
|
51
|
+
"water_v": {
|
|
52
|
+
"long_name": "northward_sea_water_velocity",
|
|
53
|
+
"standard_name": "northward_sea_water_velocity",
|
|
54
|
+
"units": "m s-1"
|
|
55
|
+
},
|
|
56
|
+
"surf_el": {
|
|
57
|
+
"long_name": "sea_surface_height_above_geoid",
|
|
58
|
+
"standard_name": "sea_surface_height_above_geoid",
|
|
59
|
+
"units": "m"
|
|
60
|
+
},
|
|
61
|
+
"water_temp": {
|
|
62
|
+
"long_name": "sea_water_temperature",
|
|
63
|
+
"standard_name": "sea_water_temperature",
|
|
64
|
+
"units": "K"
|
|
65
|
+
},
|
|
66
|
+
"salinity": {
|
|
67
|
+
"long_name": "sea_water_salinity",
|
|
68
|
+
"standard_name": "sea_water_practical_salinity",
|
|
69
|
+
"units": "1e-3"
|
|
70
|
+
},
|
|
71
|
+
# Wave parameters
|
|
72
|
+
"hs": {
|
|
73
|
+
"long_name": "significant_wave_height",
|
|
74
|
+
"standard_name": "sea_surface_wave_significant_height",
|
|
75
|
+
"units": "m"
|
|
76
|
+
},
|
|
77
|
+
"tp": {
|
|
78
|
+
"long_name": "peak_wave_period",
|
|
79
|
+
"standard_name": "sea_surface_wave_period_at_variance_spectral_density_maximum",
|
|
80
|
+
"units": "s"
|
|
81
|
+
},
|
|
82
|
+
"tm02": {
|
|
83
|
+
"long_name": "mean_wave_period_tm02",
|
|
84
|
+
"standard_name": "sea_surface_wave_mean_period_from_variance_spectral_density_second_frequency_moment",
|
|
85
|
+
"units": "s"
|
|
86
|
+
},
|
|
87
|
+
"tm10": {
|
|
88
|
+
"long_name": "mean_wave_period_tm10",
|
|
89
|
+
"standard_name": "sea_surface_wave_mean_period_from_variance_spectral_density_inverse_frequency_moment",
|
|
90
|
+
"units": "s"
|
|
91
|
+
},
|
|
92
|
+
"mwd": {
|
|
93
|
+
"long_name": "mean_wave_direction",
|
|
94
|
+
"standard_name": "sea_surface_wave_from_direction",
|
|
95
|
+
"units": "degree"
|
|
96
|
+
},
|
|
97
|
+
"pwd": {
|
|
98
|
+
"long_name": "peak_wave_direction",
|
|
99
|
+
"standard_name": "sea_surface_wave_from_direction_at_variance_spectral_density_maximum",
|
|
100
|
+
"units": "degree"
|
|
101
|
+
},
|
|
102
|
+
"spr": {
|
|
103
|
+
"long_name": "mean_wave_directional_spreading",
|
|
104
|
+
"standard_name": "sea_surface_wave_directional_spread",
|
|
105
|
+
"units": "degree"
|
|
106
|
+
}
|
|
107
|
+
}
|
tfv_get_tools/atmos.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union, Tuple, Optional, List
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from tfv_get_tools.providers._downloader import BatchDownloadResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def DownloadAtmos(
|
|
9
|
+
start_date: Union[str, datetime, pd.Timestamp],
|
|
10
|
+
end_date: Union[str, datetime, pd.Timestamp],
|
|
11
|
+
xlims: Tuple[float, float],
|
|
12
|
+
ylims: Tuple[float, float],
|
|
13
|
+
out_path: Union[str, Path] = Path("./raw"),
|
|
14
|
+
source: str = "ERA5",
|
|
15
|
+
model: str = "default",
|
|
16
|
+
prefix: Optional[str] = None,
|
|
17
|
+
verbose: bool = False,
|
|
18
|
+
variables: Optional[List[str]] = None,
|
|
19
|
+
skip_check: bool = False,
|
|
20
|
+
**kwargs,
|
|
21
|
+
) -> BatchDownloadResult:
|
|
22
|
+
"""Download Atmospheric Data - the proper user-facing API
|
|
23
|
+
|
|
24
|
+
Users should call this function, not the individual downloader classes directly.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
start_date: Start date
|
|
28
|
+
end_date: End date
|
|
29
|
+
xlims: Longitude bounds
|
|
30
|
+
ylims: Latitude bounds
|
|
31
|
+
out_path: Output directory
|
|
32
|
+
source: Data source ('ERA5', 'CFSR', 'BARRA2')
|
|
33
|
+
model: Model variant
|
|
34
|
+
prefix: File prefix
|
|
35
|
+
verbose: Verbose output
|
|
36
|
+
variables: Custom variables to download
|
|
37
|
+
skip_check: Skip user confirmation
|
|
38
|
+
**kwargs: Additional arguments
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
BatchDownloadResult: Results of the download operation
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
if source.lower() == "cfsr":
|
|
45
|
+
from tfv_get_tools.providers.atmos.cfsr import DownloadCFSRAtmos
|
|
46
|
+
|
|
47
|
+
downloader = DownloadCFSRAtmos(
|
|
48
|
+
start_date=start_date,
|
|
49
|
+
end_date=end_date,
|
|
50
|
+
xlims=xlims,
|
|
51
|
+
ylims=ylims,
|
|
52
|
+
out_path=out_path,
|
|
53
|
+
model=model,
|
|
54
|
+
prefix=prefix,
|
|
55
|
+
verbose=verbose,
|
|
56
|
+
variables=variables,
|
|
57
|
+
skip_check=skip_check,
|
|
58
|
+
**kwargs
|
|
59
|
+
)
|
|
60
|
+
return downloader.execute_download()
|
|
61
|
+
|
|
62
|
+
elif source.lower() == "era5":
|
|
63
|
+
from tfv_get_tools.providers.atmos.era5 import DownloadERA5Atmos
|
|
64
|
+
|
|
65
|
+
downloader = DownloadERA5Atmos(
|
|
66
|
+
start_date=start_date,
|
|
67
|
+
end_date=end_date,
|
|
68
|
+
xlims=xlims,
|
|
69
|
+
ylims=ylims,
|
|
70
|
+
out_path=out_path,
|
|
71
|
+
model=model,
|
|
72
|
+
prefix=prefix,
|
|
73
|
+
verbose=verbose,
|
|
74
|
+
variables=variables,
|
|
75
|
+
skip_check=skip_check,
|
|
76
|
+
**kwargs
|
|
77
|
+
)
|
|
78
|
+
return downloader.execute_download()
|
|
79
|
+
|
|
80
|
+
elif source.lower() == "barra2":
|
|
81
|
+
from tfv_get_tools.providers.atmos.barra2 import DownloadBARRA2
|
|
82
|
+
|
|
83
|
+
downloader = DownloadBARRA2(
|
|
84
|
+
start_date=start_date,
|
|
85
|
+
end_date=end_date,
|
|
86
|
+
xlims=xlims,
|
|
87
|
+
ylims=ylims,
|
|
88
|
+
out_path=out_path,
|
|
89
|
+
model=model,
|
|
90
|
+
prefix=prefix,
|
|
91
|
+
verbose=verbose,
|
|
92
|
+
variables=variables,
|
|
93
|
+
skip_check=skip_check,
|
|
94
|
+
**kwargs
|
|
95
|
+
)
|
|
96
|
+
return downloader.execute_download()
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(f'Unrecognised source {source}. Must be one of: CFSR, ERA5, BARRA2')
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def MergeAtmos(
|
|
103
|
+
in_path: Path = Path("./raw"),
|
|
104
|
+
out_path: Path = Path("."),
|
|
105
|
+
fname: str = None,
|
|
106
|
+
source: str = 'ERA5',
|
|
107
|
+
model: str = 'default',
|
|
108
|
+
time_start: str = None,
|
|
109
|
+
time_end: str = None,
|
|
110
|
+
reproject: int = None,
|
|
111
|
+
local_tz: Tuple[float, str] = None,
|
|
112
|
+
pad_dry: bool = False,
|
|
113
|
+
wrapto360: bool = False,
|
|
114
|
+
write: bool = True,
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Merge raw downloaded atmos datafiles into a single netcdf file.
|
|
118
|
+
|
|
119
|
+
**Use the same `source` and `model` that was supplied to the Downloader function**
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
in_path (Path, optional): Directory of the raw ocean data-files. Defaults to Path(".").
|
|
123
|
+
out_path (Path, optional): Output directory for the merged ocean netcdf and (opt) the fvc. Defaults to Path(".").
|
|
124
|
+
fname (str, optional): Merged ocean netcdf filename. Defaults to None.
|
|
125
|
+
source (str, optional): Source to be merged, defaults to "ERA5".
|
|
126
|
+
model (str, optional): Model for source to be merged. Defaults are listed in the wiki documentation.
|
|
127
|
+
time_start (str, optional): Start time limit of the merged dataset (str: "YYYY-mm-dd HH:MM"). Defaults to None.
|
|
128
|
+
time_end (str, optional): End time limit of the merged dataset (str: "YYYY-mm-dd HH:MM"). Defaults to None.
|
|
129
|
+
reproject (int, optional): Optionally reproject based, based on EPSG code. Defaults to None.
|
|
130
|
+
local_tz: (Tuple(float, str): optional): Add local timezone format is a tuple with Offset[float] and Label[str]
|
|
131
|
+
pad_dry: (bool, optional): Optionally pad landwards (i.e., fill nans horizontally). Defaults to False.
|
|
132
|
+
wrapto360: (bool, optional): Optionally wrap longitude to (0, 360) rather than (-180, 180). Defaults to False.
|
|
133
|
+
write (bool): Write the dataset. If False, the virtual merged dataset will be returned.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
args = tuple()
|
|
137
|
+
|
|
138
|
+
kwargs = dict(
|
|
139
|
+
in_path=in_path,
|
|
140
|
+
out_path=out_path,
|
|
141
|
+
fname=fname,
|
|
142
|
+
source=source,
|
|
143
|
+
model=model,
|
|
144
|
+
time_start=time_start,
|
|
145
|
+
time_end=time_end,
|
|
146
|
+
reproject=reproject,
|
|
147
|
+
local_tz=local_tz,
|
|
148
|
+
pad_dry=pad_dry,
|
|
149
|
+
wrapto360=wrapto360,
|
|
150
|
+
write=write,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if source.lower() == "era5":
|
|
154
|
+
from tfv_get_tools.providers.atmos.era5 import MergeERA5Atmos
|
|
155
|
+
mrg = MergeERA5Atmos(*args, **kwargs)
|
|
156
|
+
|
|
157
|
+
elif source.lower() == "cfsr":
|
|
158
|
+
from tfv_get_tools.providers.atmos.cfsr import MergeCFSRAtmos
|
|
159
|
+
mrg = MergeCFSRAtmos(*args, **kwargs)
|
|
160
|
+
|
|
161
|
+
elif source.lower() == "barra2":
|
|
162
|
+
from tfv_get_tools.providers.atmos.barra2 import MergeBARRA2
|
|
163
|
+
mrg = MergeBARRA2(*args, **kwargs)
|
|
164
|
+
|
|
165
|
+
# If the user requested no-write, return the dataset object.
|
|
166
|
+
if not write:
|
|
167
|
+
return mrg.ds
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Base CLI Class
|
|
2
|
+
|
|
3
|
+
This is where all the default arguments are setup for the CLI tools (for Downloader "A" or Merger "B")
|
|
4
|
+
|
|
5
|
+
For the mode specific arguments and messaging, go to X_cli.py program.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
import argparse
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CLIBase(ABC):
|
|
16
|
+
def __init__(self, prog_name, description):
|
|
17
|
+
self.parser = argparse.ArgumentParser(
|
|
18
|
+
prog=prog_name,
|
|
19
|
+
description=description,
|
|
20
|
+
epilog="See '<command> --help' to read about a specific sub-command.",
|
|
21
|
+
)
|
|
22
|
+
self.subparsers = self.parser.add_subparsers(
|
|
23
|
+
dest="command", help="Sub-commands"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def dir_path(self, path):
|
|
27
|
+
if Path(path).is_dir():
|
|
28
|
+
return Path(path)
|
|
29
|
+
else:
|
|
30
|
+
raise argparse.ArgumentTypeError(
|
|
31
|
+
f"--path:{path} is not a valid path - check that it exists"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def add_source_arguments(self, parser):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def add_download_parser(self, name, help_text):
|
|
39
|
+
parser = self.subparsers.add_parser(name, help=help_text)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"time_start", type=str, help='Start time in format "yyyy-mm-dd"'
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument("time_end", type=str, help='End time in format "yyyy-mm-dd"')
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"bbox",
|
|
46
|
+
nargs=4,
|
|
47
|
+
type=float,
|
|
48
|
+
help='Bounding box lon/lat extents as a list "xmin xmax ymin ymax"',
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"-p",
|
|
52
|
+
"--path",
|
|
53
|
+
default=".",
|
|
54
|
+
type=self.dir_path,
|
|
55
|
+
help="Output directory, needs to exist first",
|
|
56
|
+
)
|
|
57
|
+
self.add_source_arguments(parser)
|
|
58
|
+
parser.set_defaults(func=self.run_download)
|
|
59
|
+
|
|
60
|
+
return parser
|
|
61
|
+
|
|
62
|
+
def add_merge_parser(self, name, help_text):
|
|
63
|
+
parser = self.subparsers.add_parser(name, help=help_text)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"-f",
|
|
66
|
+
"--file_name",
|
|
67
|
+
help='Merged netcdf filename. Default: "<Type>_<time_start>_<time_end>.nc"',
|
|
68
|
+
)
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--time_start", help='Start time in format "yyyy-mm-dd"'
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument("--time_end", help='End time in format "yyyy-mm-dd"')
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"-i",
|
|
75
|
+
"--in_path",
|
|
76
|
+
default=".",
|
|
77
|
+
type=self.dir_path,
|
|
78
|
+
help="Path to the directory holding the raw data files",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"-o",
|
|
82
|
+
"--out_path",
|
|
83
|
+
default="./raw",
|
|
84
|
+
type=self.dir_path,
|
|
85
|
+
help="Output directory for the merged dataset, should exist first. Defaults to `./raw`",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"-fvc",
|
|
89
|
+
"--write_fvc",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help="Write a TUFLOW FV '.fvc' file to accompany merged dataset",
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"-rp",
|
|
95
|
+
"--reproject",
|
|
96
|
+
type=int,
|
|
97
|
+
default=None,
|
|
98
|
+
help="Reproject coordinates to a new coord system, input as EPSG code (e.g., 4326)",
|
|
99
|
+
)
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
"-tz",
|
|
102
|
+
"--timezone_offset",
|
|
103
|
+
type=float,
|
|
104
|
+
default=None,
|
|
105
|
+
help='Fixed offset hours for local timezone, e.g. "-tz 10"',
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"-ltz",
|
|
109
|
+
"--timezone_label",
|
|
110
|
+
type=str,
|
|
111
|
+
default=None,
|
|
112
|
+
help='Custom timezone label, e.g. "-ltz AEST"',
|
|
113
|
+
)
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--wrapto360",
|
|
116
|
+
action="store_true",
|
|
117
|
+
help="Format longitude as (0, 360) rather than (-180, 180)",
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--pad_dry",
|
|
121
|
+
action="store_true",
|
|
122
|
+
help="Flag to pad values out over land or dry cells by applied nearest neighbour extrapolation",
|
|
123
|
+
)
|
|
124
|
+
self.add_source_arguments(parser)
|
|
125
|
+
parser.set_defaults(func=self.run_merge)
|
|
126
|
+
|
|
127
|
+
return parser
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def run_download(self, args):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def run_merge(self, args):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def check_bbox(args):
|
|
139
|
+
"""
|
|
140
|
+
Unfortunate edge case hackfix for programmer style floats like "-28." isntead of "-28.0".
|
|
141
|
+
Fixes negative numbers with trailing dots in the final 4 arguments (bbox).
|
|
142
|
+
Only modifies if all 4 final args are numeric-like!!
|
|
143
|
+
Otherwise spits fire
|
|
144
|
+
"""
|
|
145
|
+
# Early exit if only 1 or 2 args are provided - or --help / -h
|
|
146
|
+
if len(args) == 1 or len(args) == 2 or (len(args) >= 2 and (('-h' in args) or ('--help' in args))):
|
|
147
|
+
return args
|
|
148
|
+
|
|
149
|
+
if (len(args) < 4):
|
|
150
|
+
raise ValueError("Must supply at minimum 6 arguments - time_start time_end xmin xmax ymin ymax")
|
|
151
|
+
|
|
152
|
+
# Get the last 4 arguments ( Should be bbox )
|
|
153
|
+
bbox_args = args[-4:]
|
|
154
|
+
other_args = args[:-4]
|
|
155
|
+
|
|
156
|
+
# Check if all 4 look like numbers (including problematic trailing dots)
|
|
157
|
+
numeric_pattern = r'^-?\d*\.?\d*$'
|
|
158
|
+
if not all(re.match(numeric_pattern, arg) and arg not in ['', '.', '-.', '-'] for arg in bbox_args):
|
|
159
|
+
raise ValueError("The final 4 arguments don't appear to be valid bbox coordinates. "
|
|
160
|
+
"Expected format: xmin xmax ymin ymax (numeric values)")
|
|
161
|
+
|
|
162
|
+
# Fix trailing dots by adding '0'
|
|
163
|
+
trailing_dot_pattern = r'^-?\d+\.$'
|
|
164
|
+
fixed_bbox = [arg + '0' if re.match(trailing_dot_pattern, arg) else arg for arg in bbox_args]
|
|
165
|
+
|
|
166
|
+
# Verify they're actually parseable as floats
|
|
167
|
+
try:
|
|
168
|
+
[float(arg) for arg in fixed_bbox]
|
|
169
|
+
except ValueError:
|
|
170
|
+
raise ValueError("The final 4 arguments cannot be parsed as bbox coordinates."
|
|
171
|
+
"Expected format: xmin xmax ymin ymax (numeric values)")
|
|
172
|
+
|
|
173
|
+
return other_args + fixed_bbox
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""ATMOS CLI
|
|
2
|
+
|
|
3
|
+
This tool provides two sub-programs:
|
|
4
|
+
- Downloading raw atmospheric data
|
|
5
|
+
- Merging raw atmospheric data and assisting with TUFLOW FV fvc template setup
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from argparse import ArgumentParser
|
|
9
|
+
import sys
|
|
10
|
+
import textwrap
|
|
11
|
+
|
|
12
|
+
from tfv_get_tools import DownloadAtmos, MergeAtmos
|
|
13
|
+
from tfv_get_tools.cli._cli_base import CLIBase, check_bbox
|
|
14
|
+
|
|
15
|
+
def print_atmos_info():
|
|
16
|
+
"""Returns detailed help text for GetAtmos"""
|
|
17
|
+
return textwrap.dedent("""
|
|
18
|
+
GetAtmos
|
|
19
|
+
=======================================
|
|
20
|
+
|
|
21
|
+
This tool is designed to support a user with downloading atmospheric datasets
|
|
22
|
+
from common online publicly available sources, as well as subsequent
|
|
23
|
+
pre-processing and collation for TUFLOW FV modelling.
|
|
24
|
+
The ideology of the tool is to provide the datasets in a true to original
|
|
25
|
+
raw format (program "A"), and then in a processed "simplified"
|
|
26
|
+
merged format ready for TUFLOW FV modelling (program "B").
|
|
27
|
+
|
|
28
|
+
GetAtmos works with atmospheric reanalysis and analysis data, including 10m wind
|
|
29
|
+
velocity components, mean sea-level pressure, temperature, relative humidity
|
|
30
|
+
and short/long wave downward radiation.
|
|
31
|
+
|
|
32
|
+
There are several data sources and sub-models available currently. These are listed
|
|
33
|
+
below. Some of these may require registration.
|
|
34
|
+
|
|
35
|
+
Available data sources and sub-models:
|
|
36
|
+
-------------
|
|
37
|
+
1. (Default) ECMWF's "ERA5" - registration required
|
|
38
|
+
- "default" - A global reanalysis
|
|
39
|
+
|
|
40
|
+
2. NOAA's "CFSR"
|
|
41
|
+
- "default" - A global reanalysis
|
|
42
|
+
|
|
43
|
+
3. BoM's "BARRA2"
|
|
44
|
+
- "R2" - An 11-km grid reanalysis covering Australia
|
|
45
|
+
- "C2" - An 4-km grid reanalysis covering Australia, with only wind and pressure fields downloaded.
|
|
46
|
+
- "RE2" - (Testing Only) An experimental ensembles 22-km grid covering Australia.
|
|
47
|
+
|
|
48
|
+
Example Usage:
|
|
49
|
+
---------
|
|
50
|
+
Download ERA5 Reanalysis Data - all defaults
|
|
51
|
+
`GetAtmos A 2011-01-01 2012-01-01 145 150 -30 -25`
|
|
52
|
+
|
|
53
|
+
Merge ERA5 Reanalysis Data - all defaults, merge all data in the raw folder
|
|
54
|
+
`GetAtmos B`
|
|
55
|
+
|
|
56
|
+
Download BARRA2 C2 Dataset
|
|
57
|
+
`GetAtmos A -s BARRA2 -m R2 2011-01-01 2012-01-01 150 153 -30 -25`
|
|
58
|
+
|
|
59
|
+
Merge BARRA2 C2 Dataset with reprojection and local time
|
|
60
|
+
`GetAtmos B -s BARRA2 -m R2 -tz 10 -ltz AEST -rp 7856`
|
|
61
|
+
|
|
62
|
+
For more specific help, please use:
|
|
63
|
+
`GetAtmos A -h` or `GetAtmos B -h`
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def entry():
|
|
68
|
+
"""This is the entrypoint to the CLI, linked in the pyproject.toml"""
|
|
69
|
+
cli = GetAtmos()
|
|
70
|
+
sys.argv = check_bbox(sys.argv)
|
|
71
|
+
cli.run_cli()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class GetAtmos(CLIBase):
|
|
75
|
+
def __init__(self, download_func=None, merge_func=None):
|
|
76
|
+
super().__init__("GetAtmos", "Tool for downloading and merging Atmos data")
|
|
77
|
+
|
|
78
|
+
# Allow injection of functions for testing
|
|
79
|
+
self.download_func = download_func or DownloadAtmos
|
|
80
|
+
self.merge_func = merge_func or MergeAtmos
|
|
81
|
+
|
|
82
|
+
self.add_download_parser(
|
|
83
|
+
"A",
|
|
84
|
+
"Download raw Atmos files for a set time period and bounding box extents",
|
|
85
|
+
)
|
|
86
|
+
self.add_merge_parser(
|
|
87
|
+
"B",
|
|
88
|
+
"Merge raw Atmos files into a single netcdf, and optionally write a TUFLOW FV FVC file",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self.add_info_parser()
|
|
92
|
+
|
|
93
|
+
def run_cli(self):
|
|
94
|
+
"""Parse arguments and execute the appropriate function."""
|
|
95
|
+
args = self.parser.parse_args()
|
|
96
|
+
if args.command is not None:
|
|
97
|
+
args.func(args)
|
|
98
|
+
else:
|
|
99
|
+
self.parser.print_help()
|
|
100
|
+
|
|
101
|
+
def add_info_parser(self):
|
|
102
|
+
parser = self.subparsers.add_parser('info', help="Print detailed information about this program and the options")
|
|
103
|
+
parser.set_defaults(func=self.print_detailed_info)
|
|
104
|
+
|
|
105
|
+
def print_detailed_info(self, args):
|
|
106
|
+
text = print_atmos_info()
|
|
107
|
+
print(text)
|
|
108
|
+
|
|
109
|
+
def add_source_arguments(self, parser: ArgumentParser):
|
|
110
|
+
"""Add source arguments
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
parser: The parser to add arguments to.
|
|
114
|
+
"""
|
|
115
|
+
super().add_source_arguments(parser)
|
|
116
|
+
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
'--info',
|
|
119
|
+
action="store_true",
|
|
120
|
+
help="Display the full program help"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"-s",
|
|
125
|
+
"--source",
|
|
126
|
+
type=str,
|
|
127
|
+
default="ERA5",
|
|
128
|
+
help='Atmos data source. Default = "ERA5". Optionally others see wiki.',
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"-m",
|
|
132
|
+
"--model",
|
|
133
|
+
type=str,
|
|
134
|
+
default="default",
|
|
135
|
+
help='Model from source. Default is "default". Optionally others see wiki.',
|
|
136
|
+
)
|
|
137
|
+
parser.add_argument(
|
|
138
|
+
"--test",
|
|
139
|
+
action="store_true",
|
|
140
|
+
help="Run in test mode - no actual downloads performed",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def run_download(self, args):
|
|
144
|
+
"""Call the DownloadAtmos function
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
args: CLI argument parser
|
|
148
|
+
"""
|
|
149
|
+
xlims = tuple([float(x) for x in args.bbox[:2]])
|
|
150
|
+
ylims = tuple([float(x) for x in args.bbox[2:]])
|
|
151
|
+
|
|
152
|
+
self.download_func(
|
|
153
|
+
args.time_start,
|
|
154
|
+
args.time_end,
|
|
155
|
+
xlims,
|
|
156
|
+
ylims,
|
|
157
|
+
source=args.source,
|
|
158
|
+
out_path=args.path,
|
|
159
|
+
TEST_MODE=args.test,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def run_merge(self, args):
|
|
163
|
+
"""Call the MergeAtmos function
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
args: CLI argument parser
|
|
167
|
+
"""
|
|
168
|
+
# Sort out the timezone arguments
|
|
169
|
+
if (args.timezone_offset is not None) and (args.timezone_label is not None):
|
|
170
|
+
tz = (args.timezone_offset, args.timezone_label)
|
|
171
|
+
elif args.timezone_offset is not None:
|
|
172
|
+
sign = "+" if args.timezone_offset > 0 else "-"
|
|
173
|
+
tz = (args.timezone_offset, f"UTC{sign}{abs(args.timezone_offset):0.1f}")
|
|
174
|
+
elif (args.timezone_offset is None) and (args.timezone_label is not None):
|
|
175
|
+
raise ValueError("Need to supply a timezone_offset!")
|
|
176
|
+
else:
|
|
177
|
+
tz = None
|
|
178
|
+
|
|
179
|
+
self.merge_func(
|
|
180
|
+
in_path=args.in_path,
|
|
181
|
+
out_path=args.out_path,
|
|
182
|
+
fname=args.file_name,
|
|
183
|
+
source=args.source,
|
|
184
|
+
model=args.model,
|
|
185
|
+
time_start=args.time_start,
|
|
186
|
+
time_end=args.time_end,
|
|
187
|
+
# write_fvc=args.write_fvc,
|
|
188
|
+
reproject=args.reproject,
|
|
189
|
+
local_tz=tz,
|
|
190
|
+
wrapto360=args.wrapto360,
|
|
191
|
+
pad_dry=args.pad_dry,
|
|
192
|
+
)
|