ascat 2.4.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.
Files changed (60) hide show
  1. ascat/__init__.py +16 -0
  2. ascat/aggregate/__init__.py +0 -0
  3. ascat/aggregate/aggregators.py +366 -0
  4. ascat/aggregate/interface.py +146 -0
  5. ascat/cgls.py +164 -0
  6. ascat/download/connectors.py +632 -0
  7. ascat/download/interface.py +272 -0
  8. ascat/eumetsat/__init__.py +0 -0
  9. ascat/eumetsat/level1.py +548 -0
  10. ascat/eumetsat/level2.py +404 -0
  11. ascat/file_handling.py +1003 -0
  12. ascat/h_saf.py +322 -0
  13. ascat/read_native/__init__.py +0 -0
  14. ascat/read_native/bufr.py +603 -0
  15. ascat/read_native/cdr.py +399 -0
  16. ascat/read_native/eps_native.py +2051 -0
  17. ascat/read_native/formats/ccsds.xml +26 -0
  18. ascat/read_native/formats/eps.dtd +170 -0
  19. ascat/read_native/formats/eps.xsl +2303 -0
  20. ascat/read_native/formats/eps_ascatl1bszf_13.1.xml +354 -0
  21. ascat/read_native/formats/eps_ascatl1bszf_6.4.xml +965 -0
  22. ascat/read_native/formats/eps_ascatl1bszf_6.4m.xml +965 -0
  23. ascat/read_native/formats/eps_ascatl1bszf_6.5.xml +1023 -0
  24. ascat/read_native/formats/eps_ascatl1bszf_6.6.xml +1021 -0
  25. ascat/read_native/formats/eps_ascatl1bszf_7.0.xml +557 -0
  26. ascat/read_native/formats/eps_ascatl1bszf_9.0.xml +503 -0
  27. ascat/read_native/formats/eps_ascatl1bszo_13.1.xml +394 -0
  28. ascat/read_native/formats/eps_ascatl1bszo_6.4.xml +1125 -0
  29. ascat/read_native/formats/eps_ascatl1bszo_6.4m.xml +1125 -0
  30. ascat/read_native/formats/eps_ascatl1bszo_6.5.xml +1183 -0
  31. ascat/read_native/formats/eps_ascatl1bszo_6.6.xml +1171 -0
  32. ascat/read_native/formats/eps_ascatl1bszo_7.0.xml +709 -0
  33. ascat/read_native/formats/eps_ascatl1bszo_9.0.xml +700 -0
  34. ascat/read_native/formats/eps_ascatl1bszr_13.1.xml +394 -0
  35. ascat/read_native/formats/eps_ascatl1bszr_6.4.xml +1125 -0
  36. ascat/read_native/formats/eps_ascatl1bszr_6.4m.xml +1125 -0
  37. ascat/read_native/formats/eps_ascatl1bszr_6.5.xml +1183 -0
  38. ascat/read_native/formats/eps_ascatl1bszr_6.6.xml +1171 -0
  39. ascat/read_native/formats/eps_ascatl1bszr_7.0.xml +707 -0
  40. ascat/read_native/formats/eps_ascatl1bszr_9.0.xml +698 -0
  41. ascat/read_native/formats/eps_ascatl2smo_0.1.xml +354 -0
  42. ascat/read_native/formats/eps_ascatl2smo_3b.xml +416 -0
  43. ascat/read_native/formats/eps_ascatl2smo_4.xml +414 -0
  44. ascat/read_native/formats/eps_ascatl2smr_0.1.xml +355 -0
  45. ascat/read_native/formats/eps_ascatl2smr_3b.xml +410 -0
  46. ascat/read_native/formats/eps_ascatl2smr_4.xml +408 -0
  47. ascat/read_native/hdf5.py +270 -0
  48. ascat/read_native/nc.py +580 -0
  49. ascat/read_native/ragged_array_ts.py +2572 -0
  50. ascat/read_native/xarray_io.py +1769 -0
  51. ascat/regrid/__init__.py +0 -0
  52. ascat/regrid/interface.py +138 -0
  53. ascat/regrid/regrid.py +158 -0
  54. ascat/utils.py +374 -0
  55. ascat-2.4.0.dist-info/LICENSE.txt +21 -0
  56. ascat-2.4.0.dist-info/METADATA +120 -0
  57. ascat-2.4.0.dist-info/RECORD +60 -0
  58. ascat-2.4.0.dist-info/WHEEL +5 -0
  59. ascat-2.4.0.dist-info/entry_points.txt +5 -0
  60. ascat-2.4.0.dist-info/top_level.txt +1 -0
ascat/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ import sys
2
+
3
+ if sys.version_info[:2] >= (3, 8):
4
+ # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8`
5
+ from importlib.metadata import PackageNotFoundError, version # pragma: no cover
6
+ else:
7
+ from importlib_metadata import PackageNotFoundError, version # pragma: no cover
8
+
9
+ try:
10
+ # Change here if project is renamed and does not equal the package name
11
+ dist_name = __name__
12
+ __version__ = version(dist_name)
13
+ except PackageNotFoundError: # pragma: no cover
14
+ __version__ = "unknown"
15
+ finally:
16
+ del version, PackageNotFoundError
File without changes
@@ -0,0 +1,366 @@
1
+ # Copyright (c) 2024, TU Wien
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ # * Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # * Redistributions in binary form must reproduce the above copyright
9
+ # notice, this list of conditions and the following disclaimer in the
10
+ # documentation and/or other materials provided with the distribution.
11
+ # * Neither the name of TU Wien, Department of Geodesy and Geoinformation
12
+ # nor the names of its contributors may be used to endorse or promote
13
+ # products derived from this software without specific prior written
14
+ # permission.
15
+
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ # ARE DISCLAIMED. IN NO EVENT SHALL TU WIEN DEPARTMENT OF GEODESY AND
20
+ # GEOINFORMATION BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ import datetime
29
+ import tempfile
30
+
31
+ from pathlib import Path
32
+
33
+ import numpy as np
34
+ import pandas as pd
35
+ import xarray as xr
36
+
37
+ from flox.xarray import xarray_reduce
38
+ from dask.array import unique as da_unique
39
+
40
+ import ascat.read_native.ragged_array_ts as rat
41
+ from ascat.read_native.xarray_io import get_swath_product_id
42
+ from ascat.read_native.xarray_io import dtype_to_nan
43
+
44
+
45
+ class TemporalSwathAggregator:
46
+ """Class to aggregate ASCAT data its location ids over time."""
47
+
48
+ def __init__(
49
+ self,
50
+ filepath,
51
+ start_dt,
52
+ end_dt,
53
+ t_delta,
54
+ agg,
55
+ snow_cover_mask=80,
56
+ frozen_soil_mask=80,
57
+ subsurface_scattering_mask=5,
58
+ ssm_sensitivity_mask=1,
59
+ no_masking=False,
60
+ ):
61
+ """Initialize the class.
62
+
63
+ Parameters
64
+ ----------
65
+ filepath : str
66
+ Path to the data.
67
+ start_dt : str
68
+ Start date and time (formatted e.g. 2020-01-01T00:00:00).
69
+ end_dt : str
70
+ End date and time (formatted e.g. 2020-02-01T00:00:00).
71
+ t_delta : str
72
+ Time period for aggregation (e.g. 1D, 1W, 1M, 1Y, 3M, 4Y, etc.).
73
+ agg : str
74
+ Aggregation method (e.g. mean, median, std).
75
+ snow_cover_mask : int, optional
76
+ Snow cover probability value above which to mask the source data.
77
+ frozen_soil_mask : int, optional
78
+ Frozen soil probability value above which to mask the source data.
79
+ subsurface_scattering_mask : int, optional
80
+ Subsurface scattering probability value above which to mask
81
+ the source data.
82
+ ssm_sensitivity_mask : float, optional
83
+ Soil moisture sensitivity value above which to mask
84
+ the source data.
85
+ no_masking : boolean, optional
86
+ Ignore all masks (default: False).
87
+ """
88
+ self.filepath = filepath
89
+
90
+ fmt = "%Y-%m-%dT%H:%M:%S"
91
+ self.start_dt = datetime.datetime.strptime(start_dt, fmt)
92
+ self.end_dt = datetime.datetime.strptime(end_dt, fmt)
93
+ self.timedelta = pd.Timedelta(t_delta)
94
+ self.no_masking = no_masking
95
+
96
+ agg_methods = [
97
+ "mean", "median", "mode", "std", "min", "max", "argmin", "argmax",
98
+ "quantile", "first", "last"
99
+ ]
100
+
101
+ if agg in agg_methods:
102
+ agg = "nan" + agg
103
+
104
+ self.agg = agg
105
+
106
+ # assumes ONLY swath files are in the folder
107
+ first_fname = str(next(Path(filepath).rglob("*.nc")).name)
108
+ product = get_swath_product_id(first_fname)
109
+ self.product = product
110
+
111
+ self.collection = rat.SwathFileCollection.from_product_id(
112
+ Path(filepath), product)
113
+
114
+ self.grid = self.collection.grid
115
+ self.data = None
116
+ self.agg_vars = {
117
+ "surface_soil_moisture": {
118
+ "dtype": np.dtype("int16"),
119
+ "scale_factor": 1e-2,
120
+ },
121
+ "backscatter40": {
122
+ "dtype": np.dtype("int32"),
123
+ "scale_factor": 1e-7,
124
+ },
125
+ }
126
+ self.mask_probs = {
127
+ "snow_cover_probability": snow_cover_mask,
128
+ "frozen_soil_probability": frozen_soil_mask,
129
+ "subsurface_scattering_probability": subsurface_scattering_mask,
130
+ "surface_soil_moisture_sensitivity": ssm_sensitivity_mask,
131
+ }
132
+
133
+ def _read_data(self):
134
+ """Read data."""
135
+ print("constructing dataset, this may take some time...")
136
+
137
+ self.data = self.collection.read(
138
+ date_range=(self.start_dt, self.end_dt),)
139
+
140
+ print("done constructing dataset")
141
+
142
+ def _set_metadata(self, ds):
143
+ """Add appropriate metadata to datasets."""
144
+ return ds
145
+
146
+ def _create_output_encoding(self):
147
+ """Create NetCDF encoding."""
148
+
149
+ output_encoding = {
150
+ "latitude": {
151
+ "dtype": np.dtype("int32"),
152
+ "scale_factor": 1e-6,
153
+ "zlib": True,
154
+ "complevel": 4,
155
+ "_FillValue": dtype_to_nan[np.dtype("int32")],
156
+ "missing_value": dtype_to_nan[np.dtype("int32")],
157
+ },
158
+ "longitude": {
159
+ "dtype": np.dtype("int32"),
160
+ "scale_factor": 1e-6,
161
+ "zlib": True,
162
+ "complevel": 4,
163
+ "_FillValue": dtype_to_nan[np.dtype("int32")],
164
+ "missing_value": dtype_to_nan[np.dtype("int32")],
165
+ },
166
+ "time": {
167
+ "dtype": np.dtype("float64"),
168
+ "zlib": True,
169
+ "complevel": 4,
170
+ "_FillValue": 0,
171
+ "missing_value": 0,
172
+ },
173
+ }
174
+
175
+ for var in self.agg_vars:
176
+ if var in output_encoding:
177
+ continue
178
+
179
+ output_encoding[var] = {
180
+ "dtype": self.agg_vars[var]["dtype"],
181
+ "scale_factor": self.agg_vars[var]["scale_factor"],
182
+ "zlib": True,
183
+ "complevel": 4,
184
+ "_FillValue": dtype_to_nan[self.agg_vars[var]["dtype"]],
185
+ "missing_value": dtype_to_nan[self.agg_vars[var]["dtype"]],
186
+ }
187
+
188
+ return output_encoding
189
+
190
+ def write_time_steps(self, outpath):
191
+ """
192
+ Loop through time steps and write them to file.
193
+
194
+ Parameters
195
+ ----------
196
+ outpath : str
197
+ Output path.
198
+ """
199
+ product_id = self.product.lower().replace("_", "-")
200
+ grid_sampling = str(self.collection.ioclass.grid_sampling_km) + "km"
201
+
202
+ if self.agg is not None:
203
+ datasets = self.get_aggregated_time_steps()
204
+ agg_str = f"_{self.agg}"
205
+ else:
206
+ datasets = self.get_time_steps()
207
+ agg_str = "_data"
208
+
209
+ fmt = "%Y%m%d%H%M%S"
210
+
211
+ paths = []
212
+ for ds in datasets:
213
+ step_start_str = (
214
+ np.datetime64(ds.attrs["start_time"]).astype(
215
+ datetime.datetime).strftime(fmt))
216
+ step_end_str = (
217
+ np.datetime64(ds.attrs["end_time"]).astype(
218
+ datetime.datetime).strftime(fmt))
219
+
220
+ out_name = (f"ascat"
221
+ f"_{product_id}"
222
+ f"_{grid_sampling}"
223
+ f"{agg_str}"
224
+ f"_{step_start_str}"
225
+ f"_{step_end_str}.nc")
226
+
227
+ paths.append(Path(outpath) / out_name)
228
+
229
+ print("saving datasets...", end="\r")
230
+
231
+ output_encoding = self._create_output_encoding()
232
+ xr.save_mfdataset(datasets, paths, encoding=output_encoding)
233
+ print("complete ")
234
+
235
+ return paths
236
+
237
+ def get_time_steps(self):
238
+ """
239
+ Loop through time steps of the range, return the merged data
240
+ for each unmodified.
241
+ """
242
+ time_steps = pd.date_range(
243
+ start=self.start_dt, end=self.end_dt, freq=self.timedelta)
244
+ datasets = []
245
+
246
+ if self.data is not None:
247
+ # I don't know why this case would exist, but if it does...
248
+ ds = self.data
249
+ for timestep in time_steps:
250
+ step_start = timestep
251
+ step_end = timestep + self.timedelta - pd.Timedelta("1s")
252
+ ds_step = ds.sel(time=slice(step_start, step_end))
253
+ ds_step.attrs["start_time"] = np.datetime64(step_start).astype(
254
+ str)
255
+ ds_step.attrs["end_time"] = np.datetime64(step_end).astype(str)
256
+ datasets.append(ds_step)
257
+
258
+ if self.data is None:
259
+ for timestep in time_steps:
260
+ step_start = timestep
261
+ step_end = timestep + self.timedelta
262
+ ds_step = self.collection.read(
263
+ date_range=(step_start, step_end))
264
+ step_end = step_end - pd.Timedelta("1s")
265
+ ds_step.attrs["start_time"] = np.datetime64(step_start).astype(
266
+ str)
267
+ ds_step.attrs["end_time"] = np.datetime64(step_end).astype(str)
268
+ datasets.append(ds_step)
269
+
270
+ return datasets
271
+
272
+ def get_aggregated_time_steps(self):
273
+ """Loop through data in time steps, aggregating it over time."""
274
+ if self.data is None:
275
+ self._read_data()
276
+
277
+ ds = self.data
278
+
279
+ present_agg_vars = [
280
+ var for var in self.agg_vars if var in ds.variables
281
+ ]
282
+
283
+ print("masking data...", end="\r")
284
+
285
+ global_mask = (ds.surface_flag != 0)
286
+
287
+ ds = ds.where(~global_mask, drop=False)
288
+
289
+ if not self.no_masking:
290
+ variable_masks = {
291
+ "surface_soil_moisture": (
292
+ (ds["frozen_soil_probability"]
293
+ > self.mask_probs["frozen_soil_probability"])
294
+ | (ds["snow_cover_probability"]
295
+ > self.mask_probs["snow_cover_probability"])
296
+ | (ds["subsurface_scattering_probability"]
297
+ > self.mask_probs["subsurface_scattering_probability"])
298
+ | (ds["surface_soil_moisture_sensitivity"]
299
+ < self.mask_probs["surface_soil_moisture_sensitivity"])),
300
+ }
301
+
302
+ for var, var_mask in variable_masks.items():
303
+ ds[var] = ds[var].where(~var_mask, drop=False)
304
+
305
+ print("grouping data... ")
306
+
307
+ # discretize time into integer-labeled steps according to our desired frequency
308
+ ds["time_steps"] = (
309
+ ds.time - np.datetime64(self.start_dt, "ns")) // self.timedelta
310
+
311
+ # get unique time_step and location_id values so we can tell xarray_reduce
312
+ # what to expect.
313
+ expected_time_steps = da_unique(ds["time_steps"].data).compute()
314
+ expected_location_ids = da_unique(ds["location_id"].data).compute()
315
+
316
+ # remove NaN from the expected location ids (this was introduced by the masking)
317
+ expected_location_ids = expected_location_ids[
318
+ ~np.isnan(expected_location_ids)]
319
+
320
+ # group the data by time_steps and location_id and aggregate it
321
+ grouped_ds = xarray_reduce(
322
+ ds[present_agg_vars],
323
+ ds["time_steps"],
324
+ ds["location_id"],
325
+ expected_groups=(expected_time_steps, expected_location_ids),
326
+ func=self.agg)
327
+
328
+ # convert the location_id back to an integer
329
+ grouped_ds["location_id"] = grouped_ds["location_id"].astype(int)
330
+
331
+ lons, lats = self.grid.gpi2lonlat(grouped_ds.location_id.values)
332
+ grouped_ds["longitude"] = ("location_id", lons)
333
+ grouped_ds["latitude"] = ("location_id", lats)
334
+ grouped_ds = grouped_ds.set_coords(["longitude", "latitude"])
335
+ grouped_ds = grouped_ds.chunk({"time_steps": 1})
336
+
337
+ # Compute the results, write to a temporary directory, and then read it back in.
338
+ # If we don't do this, dask will try to compute the entire dataset
339
+ # for each step of time before writing it to disk later.
340
+
341
+ with tempfile.TemporaryDirectory() as temp_dir:
342
+ temp_path = Path(temp_dir) / "ascat.nc"
343
+ print("computing results... ")
344
+ grouped_ds.to_netcdf(temp_path)
345
+ grouped_ds.close()
346
+ grouped_ds = xr.open_dataset(temp_path)
347
+
348
+ groups = []
349
+ for timestep, group in grouped_ds.groupby("time_steps", squeeze=False):
350
+ group = group.squeeze("time_steps")
351
+ print(
352
+ f"writing time step {timestep + 1}/{len(grouped_ds['time_steps'])}... ",
353
+ end="\r",
354
+ )
355
+ step_start = self.start_dt + self.timedelta * timestep
356
+ step_end = (
357
+ self.start_dt + self.timedelta * (timestep + 1) -
358
+ pd.Timedelta("1s"))
359
+ group.attrs["start_time"] = np.datetime64(step_start).astype(str)
360
+ group.attrs["end_time"] = np.datetime64(step_end).astype(str)
361
+ group["time_steps"] = np.datetime64(step_start, "ns")
362
+ group = group.rename({"time_steps": "time"})
363
+ group = self._set_metadata(group)
364
+ groups.append(group)
365
+
366
+ return groups
@@ -0,0 +1,146 @@
1
+ # Copyright (c) 2024, TU Wien
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ # * Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # * Redistributions in binary form must reproduce the above copyright
9
+ # notice, this list of conditions and the following disclaimer in the
10
+ # documentation and/or other materials provided with the distribution.
11
+ # * Neither the name of TU Wien, Department of Geodesy and Geoinformation
12
+ # nor the names of its contributors may be used to endorse or promote
13
+ # products derived from this software without specific prior written
14
+ # permission.
15
+
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ # ARE DISCLAIMED. IN NO EVENT SHALL TU WIEN DEPARTMENT OF GEODESY AND
20
+ # GEOINFORMATION BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ import sys
29
+ import argparse
30
+ from pathlib import Path
31
+
32
+ import ascat.aggregate.aggregators as aggs
33
+ from ascat.regrid.interface import swath_regrid_main
34
+
35
+
36
+ def parse_args_temporal_swath_agg(args):
37
+ parser = argparse.ArgumentParser(
38
+ description=("Generate aggregates of ASCAT swath data over "
39
+ "a given time period"))
40
+ parser.add_argument(
41
+ "filepath", metavar="FILEPATH", help="Path to the data")
42
+ parser.add_argument(
43
+ "outpath", metavar="OUTPATH", help="Path to the output data")
44
+ parser.add_argument(
45
+ "--start_dt",
46
+ metavar="START_DT",
47
+ help="Start datetime (formatted e.g. 2020-01-01T00:00:00)")
48
+ parser.add_argument(
49
+ "--end_dt",
50
+ metavar="END_DT",
51
+ help="End datetime (formatted e.g. 2020-02-01T00:00:00)")
52
+ parser.add_argument(
53
+ "--t_delta",
54
+ metavar="T_DELTA",
55
+ help="Time period for aggregation (e.g. 1D, 1W, 1M, 1Y, 2D, etc.)")
56
+ parser.add_argument("--agg", metavar="AGG", help="Aggregation")
57
+ parser.add_argument(
58
+ "--snow_cover_mask",
59
+ metavar="SNOW_COVER_MASK",
60
+ type=int,
61
+ default=90,
62
+ help=("Snow cover probability (0-100 %) value above which "
63
+ "to mask the source data (default: 90 %)"))
64
+ parser.add_argument(
65
+ "--frozen_soil_mask",
66
+ metavar="FROZEN_SOIL_MASK",
67
+ type=int,
68
+ default=90,
69
+ help=("Frozen soil probability (0-100 %) value above which "
70
+ "to mask the source data (default: 90 %)"))
71
+ parser.add_argument(
72
+ "--subsurface_scattering_mask",
73
+ metavar="SUBSURFACE_SCATTERING_MASK",
74
+ type=int,
75
+ default=10,
76
+ help=("Subsurface scattering probability (0-100 %) value above which "
77
+ "to mask the source data (default: 10 %)"))
78
+ parser.add_argument(
79
+ "--ssm_sensitivity_mask",
80
+ metavar="SSM_SENSITIVITY_MASK",
81
+ type=float,
82
+ default=1.0,
83
+ help=("Surface soil moisture sensitivity (in dB) value below which "
84
+ "to mask the source data (default: 1 dB)"))
85
+ parser.add_argument(
86
+ "--no_masking",
87
+ action='store_const',
88
+ const=True,
89
+ default=False,
90
+ help="Ignore all masks")
91
+ parser.add_argument(
92
+ "--regrid",
93
+ metavar="REGRID_DEG",
94
+ type=float,
95
+ help=("Regrid the data to a regular grid with the given "
96
+ " spacing in degrees"))
97
+ parser.add_argument(
98
+ "--grid_store",
99
+ metavar="GRID_STORE",
100
+ help=("Path to a directory for storing grids and "
101
+ "lookup tables between them"))
102
+ parser.add_argument(
103
+ "--suffix",
104
+ metavar="SUFFIX",
105
+ help="File suffix (default: _REGRID_DEGdeg)")
106
+
107
+ return parser.parse_args(args)
108
+
109
+
110
+ def temporal_swath_agg_main(cli_args):
111
+ """
112
+ Command line interface routine for temporal aggregation of ASCAT data.
113
+
114
+ Parameters
115
+ ----------
116
+ cli_args : list
117
+ Command line arguments.
118
+ """
119
+ args = parse_args_temporal_swath_agg(cli_args)
120
+
121
+ transf = aggs.TemporalSwathAggregator(
122
+ args.filepath, args.start_dt, args.end_dt, args.t_delta, args.agg,
123
+ args.snow_cover_mask, args.frozen_soil_mask,
124
+ args.subsurface_scattering_mask, args.ssm_sensitivity_mask,
125
+ args.no_masking)
126
+
127
+ outpath = Path(args.outpath)
128
+ outpath.mkdir(parents=True, exist_ok=True)
129
+
130
+ filenames = transf.write_time_steps(outpath)
131
+
132
+ if args.regrid is not None:
133
+ for filename in filenames:
134
+ regrid_args = [str(filename), str(outpath), str(args.regrid)]
135
+ if args.grid_store is not None:
136
+ regrid_args.extend(["--grid_store", args.grid_store])
137
+ if args.suffix is not None:
138
+ regrid_args.extend(["--suffix", args.suffix])
139
+ swath_regrid_main(regrid_args)
140
+
141
+
142
+ def run_temporal_swath_agg():
143
+ """
144
+ Run command line interface for temporal aggregation of ASCAT data.
145
+ """
146
+ temporal_swath_agg_main(sys.argv[1:])
ascat/cgls.py ADDED
@@ -0,0 +1,164 @@
1
+ # Copyright (c) 2024, TU Wien, Department of Geodesy and Geoinformation
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ # * Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # * Redistributions in binary form must reproduce the above copyright
9
+ # notice, this list of conditions and the following disclaimer in the
10
+ # documentation and/or other materials provided with the distribution.
11
+ # * Neither the name of TU Wien, Department of Geodesy and Geoinformation
12
+ # nor the names of its contributors may be used to endorse or promote
13
+ # products derived from this software without specific prior written
14
+ # permission.
15
+
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ # ARE DISCLAIMED. IN NO EVENT SHALL TU WIEN DEPARTMENT OF GEODESY AND
20
+ # GEOINFORMATION BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ """
29
+ CGLS SWI interface.
30
+ """
31
+
32
+ import os
33
+ import glob
34
+ import numpy as np
35
+
36
+ import pynetcf.time_series as netcdf_dataset
37
+ import pygeogrids.netcdf as netcdf
38
+
39
+
40
+ class SWI_TS(netcdf_dataset.GriddedNcOrthoMultiTs):
41
+
42
+ """
43
+ SWI TS reader for timeseries data from CGLOPS
44
+
45
+ Parameters
46
+ ----------
47
+ data_path : string
48
+ path to the netCDF files
49
+ parameters : list
50
+ list of parameters to read from netCDF file
51
+ dt : string, optional
52
+ datetime in the filenames of the cells.
53
+ If not given it is detected from the files in the data_path.
54
+ Automatic detection only works if the files follow the CGLS
55
+ naming convention.
56
+ version : string, optional
57
+ version number of the files
58
+ If not given it is detected from the files in the data_path.
59
+ Automatic detection only works if the files follow the CGLS
60
+ naming convention.
61
+ grid_fname : string, optional
62
+ filename + path of the grid netCDF file,
63
+ default is the standard grid file
64
+ (c_gls_SWI-STATIC-DGG_201501010000_GLOBE_ASCAT_V3.0.1.nc)
65
+ in the same folder as the data
66
+ read_bulk : boolean, optional
67
+ if set to true then a complete 5x5 degree cell will be read at once
68
+ providing speedup if the complete data is needed.
69
+ fname_template : string, optional
70
+ Filename template. Has to have three slots for {dt}, {version} and a
71
+ slot for the {cell} number that is available for further formatting.
72
+ The has to be without the .nc ending since this is added during reading.
73
+ cell_fn : string, optional
74
+ cell number in the fname_template.
75
+ """
76
+
77
+ def __init__(self, data_path, parameters=['SWI_001', 'SWI_005', 'SWI_010',
78
+ 'SWI_015', 'SWI_020', 'SWI_040',
79
+ 'SWI_060', 'SWI_100', 'SSF'],
80
+ dt=None, version=None,
81
+ grid_fname=None, read_bulk=True,
82
+ fname_template='c_gls_SWI-TS_{dt}_C{cell}_ASCAT_V{version}',
83
+ cell_fn='{:04d}'):
84
+
85
+ if grid_fname is None:
86
+ grid_fname = os.path.join(
87
+ data_path, 'c_gls_SWI-STATIC-DGG_201501010000_GLOBE_ASCAT_V3.0.1.nc')
88
+ grid = netcdf.load_grid(grid_fname, location_var_name='location_id',
89
+ subset_flag='land_flag')
90
+
91
+ # detect datetime and version if not given
92
+ if dt is None or version is None:
93
+ globstring = fname_template.format(dt="*",
94
+ cell="*",
95
+ version="*")
96
+ found_files = glob.glob(os.path.join(data_path, globstring))
97
+ if len(found_files) == 0:
98
+ raise IOError("No data found in {}".format(data_path))
99
+ fn = found_files[0]
100
+ fn = os.path.splitext(os.path.basename(fn))[0]
101
+ parts = fn.split('_')
102
+ if dt is None:
103
+ # this only works if the files follow the CGLS naming convention
104
+ # for everything else dt should be given as a keyword
105
+ dt = parts[3]
106
+ if version is None:
107
+ version = parts[-1][1:]
108
+
109
+ scale_factors = {'SWI_001': 0.5,
110
+ 'SWI_005': 0.5,
111
+ 'SWI_010': 0.5,
112
+ 'SWI_015': 0.5,
113
+ 'SWI_020': 0.5,
114
+ 'SWI_040': 0.5,
115
+ 'SWI_060': 0.5,
116
+ 'SWI_100': 0.5,
117
+ 'QFLAG_001': 0.5,
118
+ 'QFLAG_005': 0.5,
119
+ 'QFLAG_010': 0.5,
120
+ 'QFLAG_015': 0.5,
121
+ 'QFLAG_020': 0.5,
122
+ 'QFLAG_040': 0.5,
123
+ 'QFLAG_060': 0.5,
124
+ 'QFLAG_100': 0.5,
125
+ 'SSF': 1}
126
+
127
+ dtypes = {'SWI_001': np.uint8,
128
+ 'SWI_005': np.uint8,
129
+ 'SWI_010': np.uint8,
130
+ 'SWI_015': np.uint8,
131
+ 'SWI_020': np.uint8,
132
+ 'SWI_040': np.uint8,
133
+ 'SWI_060': np.uint8,
134
+ 'SWI_100': np.uint8,
135
+ 'QFLAG_001': np.uint8,
136
+ 'QFLAG_005': np.uint8,
137
+ 'QFLAG_010': np.uint8,
138
+ 'QFLAG_015': np.uint8,
139
+ 'QFLAG_020': np.uint8,
140
+ 'QFLAG_040': np.uint8,
141
+ 'QFLAG_060': np.uint8,
142
+ 'QFLAG_100': np.uint8,
143
+ 'SSF': np.uint8}
144
+
145
+ super(SWI_TS, self).__init__(
146
+ data_path, grid,
147
+ fn_format=fname_template.format(dt=dt, version=version,
148
+ cell=cell_fn),
149
+ parameters=parameters, scale_factors=scale_factors,
150
+ dtypes=dtypes, autoscale=False,
151
+ automask=False, ioclass_kws={'read_bulk': read_bulk,
152
+ 'loc_ids_name': 'locations'})
153
+
154
+ def _read_gp(self, gpi, period=None, mask_frozen=True):
155
+ data = super(SWI_TS, self)._read_gp(gpi, period=period)
156
+
157
+ if mask_frozen is True:
158
+ unfrozen = data['SSF'].values <= 1
159
+ data = data[unfrozen]
160
+
161
+ for column in data:
162
+ data.loc[data[column] > 100, column] = np.nan
163
+
164
+ return data