• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TUW-GEO / ecmwf_models / 11944977952

06 Nov 2024 01:46PM CUT coverage: 80.452%. Remained the same
11944977952

push

github

web-flow
Update CHANGELOG.rst

189 of 300 branches covered (63.0%)

712 of 885 relevant lines covered (80.45%)

4.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.28
/src/ecmwf_models/interface.py
1
# The MIT License (MIT)
2
#
3
# Copyright (c) 2019, TU Wien, Department of Geodesy and Geoinformation
4
#
5
# Permission is hereby granted, free of charge, to any person obtaining a copy
6
# of this software and associated documentation files (the "Software"), to deal
7
# in the Software without restriction, including without limitation the rights
8
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
# copies of the Software, and to permit persons to whom the Software is
10
# furnished to do so, subject to the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included in all
13
# copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
# SOFTWARE.
22
"""
23
Base classes for reading downloaded ERA netcdf and grib images and stacks
24
"""
25
import warnings
6✔
26
import os
6✔
27
import glob
6✔
28
from datetime import timedelta, datetime  # noqa: F401
6✔
29
import numpy as np
6✔
30
import xarray as xr
6✔
31

32
from pygeobase.io_base import ImageBase, MultiTemporalImageBase
6✔
33
from pygeobase.object_base import Image
6✔
34
from pynetcf.time_series import GriddedNcOrthoMultiTs
6✔
35
from pygeogrids.grids import BasicGrid, gridfromdims
6✔
36
from pygeogrids.netcdf import load_grid
6✔
37

38
from ecmwf_models.grid import trafo_lon
6✔
39
from ecmwf_models.utils import lookup
6✔
40
from ecmwf_models.globals import (
6✔
41
    IMG_FNAME_TEMPLATE,
42
    IMG_FNAME_DATETIME_FORMAT,
43
    SUPPORTED_PRODUCTS,
44
    SUBDIRS,
45
)
46
from ecmwf_models.globals import (pygrib, pygrib_available,
6✔
47
                                  PygribNotFoundError)
48

49

50
class ERANcImg(ImageBase):
6✔
51
    """
52
    Reader for a single ERA netcdf file. The main purpose of this class is
53
    to use it in the time series conversion routine. To read downloaded image
54
    files, we recommend using xarray (https://docs.xarray.dev/en/stable/).
55

56
    Parameters
57
    ----------
58
    filename: str
59
        Path to the image file to read.
60
    product : str
61
        'era5' or 'era5-land'
62
    parameter: list or str, optional (default: ['swvl1', 'swvl2'])
63
        Name of parameters to read from the image file.
64
    subgrid: ERA_RegularImgGrid or ERA_RegularImgLandGrid or None, optional
65
        Read only data for points of this grid.
66
        If None is passed, we read all points from the file.
67
        The main purpose of this parameter is when reshuffling to time series,
68
        to include only e.g. points over land.
69
    mask_seapoints : bool, optional (default: False)
70
        Read the land-sea mask to mask points over water and set them to nan.
71
        This option needs the 'lsm' parameter to be in the file!
72
    array_1D: bool, optional (default: False)
73
        Read data as list, instead of 2D array, used for reshuffling.
74
    mode : str, optional (default: 'r')
75
        Mode in which to open the file, changing this can cause data loss.
76
        This argument should not be changed!
77
    """
78

79
    def __init__(
6✔
80
        self,
81
        filename,
82
        product,
83
        parameter=None,
84
        subgrid=None,
85
        mask_seapoints=False,
86
        array_1D=False,
87
        mode='r',
88
    ):
89

90
        super(ERANcImg, self).__init__(filename, mode=mode)
6✔
91

92
        if parameter is not None:
6!
93
            # look up short names
94
            self.parameter = lookup(
6✔
95
                product, np.atleast_1d(parameter))["short_name"].values
96
        else:
97
            self.parameter = None
×
98

99
        self.mask_seapoints = mask_seapoints
6✔
100
        self.array_1D = array_1D
6✔
101
        self.subgrid = subgrid
6✔
102

103
        if self.subgrid and not self.array_1D:
6!
104
            warnings.warn(
×
105
                "Reading spatial subsets as 2D arrays ony works if there "
106
                "is an equal number of points in each line")
107

108
    def read(self, timestamp=None):
6✔
109
        """
110
        Read data from the loaded image file.
111

112
        Parameters
113
        ---------
114
        timestamp : datetime, optional (default: None)
115
            Specific date (time) to read the data for.
116
        """
117

118
        try:
6✔
119
            dataset = xr.open_dataset(
6✔
120
                self.filename, engine="netcdf4", mask_and_scale=True)
121
        except IOError as e:
×
122
            print(" ".join([self.filename, "can not be opened"]))
×
123
            raise e
×
124

125
        if self.parameter is None:
6!
126
            self.parameter = list(dataset.data_vars)
×
127

128
        if self.mask_seapoints:
6✔
129
            if "lsm" not in dataset.variables.keys():
6!
130
                raise IOError("No land sea mask parameter (lsm) in"
×
131
                              " passed image for masking.")
132
            else:
133
                sea_mask = dataset.variables["lsm"].values
6✔
134
        else:
135
            sea_mask = None
6✔
136

137
        return_img = {}
6✔
138
        return_metadata = {}
6✔
139

140
        grid = gridfromdims(
6✔
141
            trafo_lon(dataset['longitude'].values),
142
            dataset['latitude'].values,
143
            origin='top')
144

145
        if self.subgrid is not None:
6✔
146
            gpis = grid.find_nearest_gpi(self.subgrid.activearrlon,
6✔
147
                                         self.subgrid.activearrlat)[0]
148
        else:
149
            gpis = None
6✔
150

151
        for name in self.parameter:
6✔
152
            try:
6✔
153
                variable = dataset[name]
6✔
154
            except KeyError:
×
155
                path, f = os.path.split(self.filename)
×
156
                warnings.warn(f"Cannot load variable {name} from file {f}. "
×
157
                              f"Filling image with NaNs.")
158
                dat = np.full(
×
159
                    grid.shape if gpis is None else len(gpis), np.nan)
160
                return_img[name] = dat
×
161
                continue
×
162

163
            if 'expver' in variable.dims and (variable.data.ndim == 3):
6!
164
                warnings.warn(f"Found experimental data in {self.filename}")
×
165
                param_data = variable.data[-1]
×
166
                for vers_data in variable.data:
×
167
                    if not np.all(np.isnan(vers_data)):
×
168
                        param_data = vers_data
×
169
            else:
170
                param_data = variable.data
6✔
171

172
            if self.mask_seapoints:
6✔
173
                param_data = np.ma.array(
6✔
174
                    param_data,
175
                    mask=np.logical_not(sea_mask),
176
                    fill_value=np.nan,
177
                )
178
                param_data = param_data.filled()
6✔
179

180
            param_data = param_data.flatten()
6✔
181

182
            if gpis is not None:
6✔
183
                param_data = param_data[gpis]
6✔
184

185
            return_metadata[name] = variable.attrs
6✔
186
            return_img[name] = param_data
6✔
187

188
        dataset.close()
6✔
189

190
        if self.subgrid is None:
6✔
191
            self.subgrid = grid
6✔
192

193
        if self.array_1D:
6✔
194
            return Image(
6✔
195
                self.subgrid.activearrlon,
196
                self.subgrid.activearrlat,
197
                return_img,
198
                return_metadata,
199
                timestamp,
200
            )
201
        else:
202
            if len(self.subgrid.shape) != 2:
6!
203
                raise ValueError("Passed subgrid does not have a 2d shape."
×
204
                                 "Did you mean to read data as 1d arrays?")
205

206
            for key in return_img:
6✔
207
                return_img[key] = return_img[key].reshape(self.subgrid.shape)
6✔
208

209
            return Image(
6✔
210
                self.subgrid.activearrlon.reshape(self.subgrid.shape),
211
                self.subgrid.activearrlat.reshape(self.subgrid.shape),
212
                return_img,
213
                return_metadata,
214
                timestamp,
215
            )
216

217
    def write(self, data):
6✔
218
        raise NotImplementedError()
219

220
    def flush(self):
6✔
221
        pass
×
222

223
    def close(self):
6✔
224
        pass
6✔
225

226

227
class ERANcDs(MultiTemporalImageBase):
6✔
228
    """
229
    Reader to extract individual images from a multi-image netcdf dataset.
230
    The main purpose of this class is to use it in the time series conversion
231
    routine. To read downloaded image files, we recommend using
232
    xarray (https://docs.xarray.dev/en/stable/).
233

234
    Parameters
235
    ----------
236
    root_path: str
237
        Root path where image data is stored.
238
    product: str
239
        ERA5 or ERA5-LAND
240
    parameter: list[str] or str, optional (default: None)
241
        Parameter or list of parameters to read. None reads all available
242
        Parameters.
243
    subgrid: ERA_RegularImgGrid or ERA_RegularImgLandGrid, optional
244
        Read only data for points of this grid.
245
        If None is passed, we read all points from the file.
246
        The main purpose of this parameter is when reshuffling to time series,
247
        to include only e.g. points over land.
248
    mask_seapoints: bool, optional (default: False)
249
        All points that are not over land are replaced with NaN values.
250
        This requires that the land sea mask (lsm) parameter is included
251
        in the image files!
252
    h_steps: tuple, optional (default: (0, 6, 12, 18))
253
        Time stamps available for each day. Numbers refer to full hours.
254
    array_1D: bool, optional (default: True)
255
        Read data as 1d arrays. This is required when the passed subgrid
256
        is 1-dimensional (e.g. when only landpoints are read). Otherwise
257
        when a 2d (subgrid) is used, this switch means that the extracted
258
        image data is also 2-dimensional (lon, lat).
259
    """
260

261
    def __init__(
6✔
262
            self,
263
            root_path,
264
            product,
265
            parameter=None,
266
            subgrid=None,
267
            mask_seapoints=False,
268
            h_steps=(0, 6, 12, 18),
269
            array_1D=False,
270
    ):
271

272
        self.h_steps = h_steps
6✔
273

274
        if parameter is not None:
6!
275
            # look up short names
276
            self.parameter = lookup(
6✔
277
                product, np.atleast_1d(parameter))["short_name"].values
278
        else:
279
            self.parameter = None
×
280

281
        ioclass_kws = {
6✔
282
            'product': product,
283
            'parameter': parameter,
284
            'subgrid': subgrid,
285
            'mask_seapoints': mask_seapoints,
286
            'array_1D': array_1D
287
        }
288

289
        # the goal is to use ERA5-T*.nc if necessary, but prefer ERA5*.nc
290
        self.fn_templ_priority = [
6✔
291
            IMG_FNAME_TEMPLATE.format(
292
                product=(p + ext).upper(),
293
                type='*',
294
                datetime="{datetime}",
295
                ext='nc') for ext in ['', '-T'] for p in SUPPORTED_PRODUCTS
296
        ]
297

298
        super(ERANcDs, self).__init__(
6✔
299
            root_path,
300
            ERANcImg,
301
            fname_templ=IMG_FNAME_TEMPLATE.format(
302
                product='*', type='*', datetime='{datetime}', ext='nc'),
303
            datetime_format=IMG_FNAME_DATETIME_FORMAT,
304
            subpath_templ=SUBDIRS,
305
            exact_templ=False,
306
            ioclass_kws=ioclass_kws)
307

308
    def _search_files(self,
6✔
309
                      timestamp,
310
                      custom_templ=None,
311
                      str_param=None,
312
                      custom_datetime_format=None):
313
        """
314
        override the original filename generation to allow multiple files for
315
        time stamp
316
        """
317
        if custom_templ is not None:
6✔
318
            raise NotImplementedError
319
        else:
320
            fname_templ = self.fname_templ
6✔
321

322
        if custom_datetime_format is not None:
6!
323
            dFormat = {self.dtime_placeholder: custom_datetime_format}
×
324

325
        else:
326
            dFormat = {self.dtime_placeholder: self.datetime_format}
6✔
327

328
        sub_path = ''
6✔
329
        if self.subpath_templ is not None:
6!
330
            for s in self.subpath_templ:
6✔
331
                sub_path = os.path.join(sub_path, timestamp.strftime(s))
6✔
332

333
        fname_templ = fname_templ.format(**dFormat)
6✔
334

335
        if str_param is not None:
6!
336
            fname_templ = fname_templ.format(**str_param)
×
337

338
        search_file = os.path.join(self.path, sub_path,
6✔
339
                                   timestamp.strftime(fname_templ))
340

341
        if self.exact_templ:
6✔
342
            raise NotImplementedError
343
        else:
344
            filename = glob.glob(search_file)
6✔
345
            if len(filename) > 1:
6✔
346
                for templ in self.fn_templ_priority:
6!
347
                    fname_templ = templ.format(**dFormat)
6✔
348
                    if str_param is not None:
6!
349
                        fname_templ = fname_templ.format(**str_param)
×
350
                    search_file = os.path.join(self.path, sub_path,
6✔
351
                                               timestamp.strftime(fname_templ))
352
                    filename = glob.glob(search_file)
6✔
353
                    if len(filename) == 1:
6!
354
                        break
6✔
355

356
        if not filename:
6!
357
            filename = []
×
358

359
        return filename
6✔
360

361
    def tstamps_for_daterange(self, start_date, end_date):
6✔
362
        """
363
        Get datetimes in the correct sub-daily resolution between 2 dates
364

365
        Parameters
366
        ----------
367
        start_date: datetime
368
            Start datetime
369
        end_date: datetime
370
            End datetime
371

372
        Returns
373
        ----------
374
        timestamps : list
375
            List of datetimes
376
        """
377

378
        img_offsets = np.array([timedelta(hours=h) for h in self.h_steps])
6✔
379

380
        timestamps = []
6✔
381
        diff = end_date - start_date
6✔
382
        for i in range(diff.days + 1):
6✔
383
            daily_dates = start_date + timedelta(days=i) + img_offsets
6✔
384
            timestamps.extend(daily_dates.tolist())
6✔
385

386
        return timestamps
6✔
387

388

389
class ERAGrbImg(ImageBase):
6✔
390

391
    def __init__(self,
6✔
392
                 filename,
393
                 product,
394
                 parameter=None,
395
                 subgrid=None,
396
                 mask_seapoints=False,
397
                 array_1D=True,
398
                 mode='r'):
399
        """
400
        Reader for a single ERA grib file. The main purpose of this class is
401
        to use it in the time series conversion routine. To read downloaded image
402
        files, we recommend using xarray (https://docs.xarray.dev/en/stable/).
403

404
        Parameters
405
        ----------
406
        filename: str
407
            Path to the image file to read.
408
        product : str
409
            ERA5 or ERA5-LAND
410
        parameter: list or str, optional
411
            Name of parameters to read from the image file. None means all
412
            available data variables.
413
        subgrid: ERA_RegularImgGrid or ERA_RegularImgLandGrid, optional (default: None)
414
            Read only data for points of this grid.
415
            If None is passed, we read all points from the file.
416
        mask_seapoints : bool, optional (default: False)
417
            Read the land-sea mask to mask points over water and set them to nan.
418
            This option needs the 'lsm' parameter to be in the file!
419
        array_1D: bool, optional (default: False)
420
            Read data as list, instead of 2D array, used for reshuffling.
421
        mode : str, optional (default: 'r')
422
            Mode in which to open the file, changing this can cause data loss.
423
            This argument should not be changed!
424
        """
425
        super(ERAGrbImg, self).__init__(filename, mode=mode)
6✔
426

427
        if parameter is None:
6!
428
            self.parameter = None
×
429
        else:
430
            # look up short names
431
            self.parameter = lookup(
6✔
432
                product, np.atleast_1d(parameter))["short_name"].values
433

434
        self.product = product
6✔
435

436
        self.mask_seapoints = mask_seapoints
6✔
437
        self.array_1D = array_1D
6✔
438
        self.subgrid = subgrid
6✔
439

440
    def read(self, timestamp=None):
6✔
441
        """
442
        Read data from the loaded image file.
443

444
        Parameters
445
        ---------
446
        timestamp : datetime, optional (default: None)
447
            Specific date (time) to read the data for.
448
        """
449
        if not pygrib_available:
6!
450
            raise PygribNotFoundError()
×
451
        grbs = pygrib.open(self.filename)
6✔
452

453
        return_img = {}
6✔
454
        return_metadata = {}
6✔
455

456
        grid = None
6✔
457

458
        for n in range(1, grbs.messages + 1):
6✔
459
            message = grbs.message(n)
6✔
460
            try:
6✔
461
                param_name = str(message.cfVarNameECMF)  # old field?
6✔
462
            except RuntimeError:
6✔
463
                param_name = str(message.shortName)
6✔
464

465
            if self.mask_seapoints and (param_name == "lsm"):
6✔
466
                pass
6✔
467
            elif self.parameter is None:
6!
468
                pass
×
469
            elif param_name in self.parameter:
6✔
470
                pass
6✔
471
            else:
472
                continue
3✔
473

474
            return_metadata[param_name] = {}
6✔
475

476
            message = grbs.message(n)
6✔
477
            lats, lons = message.latlons()
6✔
478
            param_data = message.values
6✔
479

480
            if grid is None:
6✔
481
                grid = BasicGrid(
6✔
482
                    trafo_lon(lons).flatten(),
483
                    lats.flatten(),
484
                    shape=param_data.shape)
485

486
            param_data = param_data.flatten()
6✔
487

488
            if self.subgrid is not None:
6✔
489
                gpis = grid.find_nearest_gpi(self.subgrid.activearrlon,
6✔
490
                                             self.subgrid.activearrlat)[0]
491

492
                param_data = param_data[gpis]
6✔
493

494
            return_img[param_name] = param_data
6✔
495

496
            return_metadata[param_name]["units"] = message["units"]
6✔
497
            return_metadata[param_name]["long_name"] = \
6✔
498
                message["parameterName"]
499

500
            if "levels" in message.keys():
6✔
501
                return_metadata[param_name]["depth"] = "{:} cm".format(
6✔
502
                    message["levels"])
503

504
        grbs.close()
6✔
505

506
        # Set data for non-land points to NaN
507
        if self.mask_seapoints:
6✔
508
            if 'lsm' not in return_img:
6!
509
                raise IOError(
×
510
                    "No land sea mask parameter (lsm) in passed image"
511
                    " for masking.")
512
            else:
513
                # mask the loaded data
514
                mask = np.logical_not(return_img['lsm'].flatten())
6✔
515
                for name in return_img.keys():
6✔
516
                    param_data = return_img[name]
6✔
517
                    param_data = np.ma.array(
6✔
518
                        param_data,
519
                        mask=mask,
520
                        fill_value=np.nan,
521
                    )
522
                    param_data = param_data.filled()
6✔
523
                    return_img[name] = param_data
6✔
524

525
            if (self.parameter is not None) and ('lsm' not in self.parameter):
6!
526
                return_img.pop('lsm')
6✔
527
                return_metadata.pop('lsm')
6✔
528

529
        if self.subgrid is None:
6✔
530
            self.subgrid = grid
6✔
531

532
        # Add empty arrays for missing variables
533
        if self.parameter is not None:
6!
534
            for p in self.parameter:
6✔
535
                if p not in return_img:
6!
536
                    param_data = np.full(np.prod(self.subgrid.shape), np.nan)
×
537
                    warnings.warn(
×
538
                        f"Cannot load variable {param_name} from file "
539
                        f"{self.filename}. Filling image with NaNs.")
540
                    return_img[param_name] = param_data
×
541
                    return_metadata[param_name] = {}
×
542
                    return_metadata[param_name]["long_name"] = lookup(
×
543
                        self.product, [param_name]).iloc[0]["long_name"]
544

545
        if self.array_1D:
6✔
546
            return Image(
6✔
547
                self.subgrid.activearrlon,
548
                self.subgrid.activearrlat,
549
                return_img,
550
                return_metadata,
551
                timestamp,
552
            )
553
        else:
554
            if len(self.subgrid.shape) != 2:
6!
555
                raise ValueError("Passed subgrid does not have a 2d shape."
×
556
                                 "Did you mean to read data as 1d arrays?")
557

558
            for key in return_img:
6✔
559
                return_img[key] = return_img[key].reshape(self.subgrid.shape)
6✔
560

561
            return Image(
6✔
562
                self.subgrid.activearrlon.reshape(self.subgrid.shape),
563
                self.subgrid.activearrlat.reshape(self.subgrid.shape),
564
                return_img,
565
                return_metadata,
566
                timestamp,
567
            )
568

569
    def write(self, data):
6✔
570
        raise NotImplementedError()
571

572
    def flush(self):
6✔
573
        pass
×
574

575
    def close(self):
6✔
576
        pass
6✔
577

578

579
class ERAGrbDs(MultiTemporalImageBase):
6✔
580

581
    def __init__(
6✔
582
            self,
583
            root_path,
584
            product,
585
            parameter=None,
586
            subgrid=None,
587
            mask_seapoints=False,
588
            h_steps=(0, 6, 12, 18),
589
            array_1D=True,
590
    ):
591
        """
592
        Reader to extract individual images from a multi-image grib dataset.
593
        The main purpose of this class is to use it in the time series conversion
594
        routine. To read downloaded image files, we recommend using
595
        xarray (https://docs.xarray.dev/en/stable/).
596

597
        Parameters
598
        ----------
599
        root_path: str
600
            Root path where the downloaded image data is stored in grib format.
601
            We assume that image files are organized in subfolders by year,
602
            with each year containing subfolders for individual days of the
603
            year.
604
        product : str
605
            ERA5 or ERA5-Land
606
        parameter: list[str] or str, optional (default: None)
607
            Parameter or list of parameters to read. None reads all available
608
            Parameters.
609
        subgrid: ERA_RegularImgGrid or ERA_RegularImgLandGrid, optional
610
            Read only data for points of this grid.
611
            If None is passed, we read all points from the file.
612
        mask_seapoints: bool, optional (default: False)
613
            All points that are not over land are replaced with NaN values.
614
            This requires that the land sea mask (lsm) parameter is included
615
            in the image files!
616
        h_steps: tuple, optional (default: (0, 6, 12, 18))
617
            Time stamps available for each day. Numbers refer to full hours.
618
        array_1D: bool, optional (default: True)
619
            Read data as 1d arrays. This is required when the passed subgrid
620
            is 1-dimensional (e.g. when only landpoints are read). Otherwise
621
            when a 2d (subgrid) is used, this switch means that the extracted
622
            image data is also 2-dimensional (lon, lat).
623
        """
624
        self.h_steps = h_steps
6✔
625

626
        ioclass_kws = {
6✔
627
            "product": product,
628
            "parameter": parameter,
629
            "subgrid": subgrid,
630
            "mask_seapoints": mask_seapoints,
631
            "array_1D": array_1D,
632
        }
633

634
        fname_templ = IMG_FNAME_TEMPLATE.format(
6✔
635
            product="*", type="*", datetime="{datetime}", ext="grb")
636

637
        super(ERAGrbDs, self).__init__(
6✔
638
            root_path,
639
            ERAGrbImg,
640
            fname_templ=fname_templ,
641
            datetime_format=IMG_FNAME_DATETIME_FORMAT,
642
            subpath_templ=SUBDIRS,
643
            exact_templ=False,
644
            ioclass_kws=ioclass_kws,
645
        )
646

647
    def tstamps_for_daterange(self, start_date, end_date):
6✔
648
        """
649
        Get datetimes in the correct sub-daily resolution between 2 dates
650

651
        Parameters
652
        ----------
653
        start_date: datetime
654
            Start datetime
655
        end_date: datetime
656
            End datetime
657

658
        Returns
659
        ----------
660
        timestamps : list
661
            List of datetime values (between start and end date) for all
662
            required time stamps.
663
        """
664
        img_offsets = np.array([timedelta(hours=h) for h in self.h_steps])
6✔
665

666
        timestamps = []
6✔
667
        diff = end_date - start_date
6✔
668
        for i in range(diff.days + 1):
6✔
669
            daily_dates = start_date + timedelta(days=i) + img_offsets
6✔
670
            timestamps.extend(daily_dates.tolist())
6✔
671

672
        return timestamps
6✔
673

674

675
class ERATs(GriddedNcOrthoMultiTs):
6✔
676
    """
677
    Time series reader for all reshuffled ERA reanalysis products in time
678
    series format (pynetcf OrthoMultiTs format)
679
    Use the read_ts(lon, lat) resp. read_ts(gpi) function of this class
680
    to read data for a location!
681
    """
682

683
    def __init__(self, ts_path, grid_path=None, **kwargs):
6✔
684
        """
685
        Parameters
686
        ----------
687
        ts_path : str
688
            Directory where the netcdf time series files are stored
689
        grid_path : str, optional (default: None)
690
            Path to grid file, that is used to organize the location of time
691
            series to read. If None is passed, grid.nc is searched for in the
692
            ts_path.
693

694
        Optional keyword arguments that are passed to the Gridded Base when used
695
        ------------------------------------------------------------------------
696
        parameters : list, optional (default: None)
697
            Specific variable names to read, if None are selected,
698
            all are read.
699
        offsets : dict, optional (default: None)
700
            Offsets (values) that are added to the parameters (keys)
701
        scale_factors : dict, optional (default: None)
702
            Offset (value) that the parameters (key) is multiplied with
703
        ioclass_kws: dict, (optional)
704

705
        Optional keyword arguments, passed to the OrthoMultiTs class when used
706
        ----------------------------------------------------------------------
707
            read_bulk : boolean, optional (default: False)
708
                If set to True, the data of all locations is read into memory,
709
                and subsequent calls to read_ts then read from cache and
710
                not from disk. This makes reading complete files faster.
711
            read_dates : boolean, optional (default: False)
712
                If false, dates will not be read automatically but only on
713
                specific request useable for bulk reading because currently
714
                the netCDF num2date routine is very slow for big datasets.
715
        """
716
        if grid_path is None:
6!
717
            grid_path = os.path.join(ts_path, "grid.nc")
6✔
718
        grid = load_grid(grid_path)
6✔
719

720
        super(ERATs, self).__init__(ts_path, grid, **kwargs)
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc