pygtide 0.8.2__cp314-cp314-win_amd64.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.
- pygtide/__init__.py +35 -0
- pygtide/commdat/[raw]_Leap_Second_History.dat +41 -0
- pygtide/commdat/[raw]_eopc04_IAU2000.dat +22052 -0
- pygtide/commdat/[raw]_finals2000A.dat +18472 -0
- pygtide/commdat/buellehw.dat +724 -0
- pygtide/commdat/cted73hw.dat +594 -0
- pygtide/commdat/doodsehw.dat +448 -0
- pygtide/commdat/etddt.dat +236 -0
- pygtide/commdat/etddt_tmpl.dat +208 -0
- pygtide/commdat/etpolut1.dat +22987 -0
- pygtide/commdat/hw95s.dat +13146 -0
- pygtide/commdat/ksm03.dat +28880 -0
- pygtide/commdat/ratgp95.dat +6567 -0
- pygtide/commdat/tamurahw.dat +1268 -0
- pygtide/commdat/xi1989hw.dat +3004 -0
- pygtide/core.py +539 -0
- pygtide/etpred.pyd +0 -0
- pygtide/tests.py +70 -0
- pygtide/update_etpred_data.py +515 -0
- pygtide-0.8.2.dist-info/DELVEWHEEL +2 -0
- pygtide-0.8.2.dist-info/METADATA +124 -0
- pygtide-0.8.2.dist-info/RECORD +29 -0
- pygtide-0.8.2.dist-info/WHEEL +5 -0
- pygtide-0.8.2.dist-info/licenses/LICENSE +373 -0
- pygtide-0.8.2.dist-info/top_level.txt +1 -0
- pygtide.libs/libgcc_s_seh-1.dll +0 -0
- pygtide.libs/libgfortran-5.dll +0 -0
- pygtide.libs/libquadmath-0.dll +0 -0
- pygtide.libs/libwinpthread-1.dll +0 -0
pygtide/core.py
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyGTide - A python class to calculate time series of the gravitational tides on Earth
|
|
3
|
+
-------------------------------------------------------------------------------
|
|
4
|
+
Author: Gabriel C. Rau (gabriel@hydrogeo.science)
|
|
5
|
+
Website: https://hydrogeo.science
|
|
6
|
+
Based on:
|
|
7
|
+
ETERNA 3.4 PREDICT (by Prof. Wenzel, 1996)
|
|
8
|
+
Updated updated by Kudryevtsev (2004)
|
|
9
|
+
|
|
10
|
+
Publications:
|
|
11
|
+
Wenzel, H.-G. The nanogal software: Earth tide data processing package ETERNA 3.30,
|
|
12
|
+
Bull. Inf. Marées Terrestres (1996) 124, 9425–9439.
|
|
13
|
+
Kudryavtsev, S. Journal of Geodesy (2004) 77: 829. https://doi.org/10.1007/s00190-003-0361-2
|
|
14
|
+
-------------------------------------------------------------------------------
|
|
15
|
+
How to run:
|
|
16
|
+
import pygtide
|
|
17
|
+
pt = pygtide.pygtide()
|
|
18
|
+
pt.predict(latitude, longitude, height, startdate, duration, samprate, **control):
|
|
19
|
+
data = pt.results()
|
|
20
|
+
|
|
21
|
+
pt.results() returns:
|
|
22
|
+
either False, or a Pandas dataframe with the respective data.
|
|
23
|
+
|
|
24
|
+
Python status messages can be suppressed by setting: self.msg = False.
|
|
25
|
+
The routine relies on files in the subdirectory 'commdat'. This includes the tidal
|
|
26
|
+
catalogues, corrections of the pole rotation as well as leap seconds.
|
|
27
|
+
Wave group parameters are being read from the "self.waves.ini" file.
|
|
28
|
+
-------------------------------------------------------------------------------
|
|
29
|
+
The original code was written in Fortran 77/90 and is available for download from:
|
|
30
|
+
http://igets.u-strasbg.fr/soft_and_tool.php
|
|
31
|
+
-------------------------------------------------------------------------------
|
|
32
|
+
Subroutines used in the Fortran program:
|
|
33
|
+
--- new ---
|
|
34
|
+
PREDICT: New Python interface subroutine to hand over arguments and calculate
|
|
35
|
+
INIT: New subroutine which sets the variables in module out for access in Python.
|
|
36
|
+
--- existing ---
|
|
37
|
+
ETASTN: computes astronomical elements.
|
|
38
|
+
PREDIN: reads control parameters.
|
|
39
|
+
ETDDTA: reads tabel of DDT = TDT - UTC.
|
|
40
|
+
ETDDTB: interpolates DDT = TDT - UTC from table.
|
|
41
|
+
ETGCON: computes geodetic coefficients.
|
|
42
|
+
ETGREN: computes date from Julian date.
|
|
43
|
+
ETJULN: computes JULIAN date.
|
|
44
|
+
ETLEGN: computes fully normalized Legendre spherical harmonics.
|
|
45
|
+
ETLOVE: computes elastic parameters from Wahr-Dehant model.
|
|
46
|
+
ETPOLC: computes DUT1.
|
|
47
|
+
ETPHAS: computes the phases and frequencies of the tidal waves.
|
|
48
|
+
ETPOTS: computes amplitudes, frequencies and phases of tidal waves.
|
|
49
|
+
GEOEXT: computes JOBTIME.
|
|
50
|
+
===============================================================================
|
|
51
|
+
Note: While Prof. Wenzel passed away, his legacy continues to live as PyGTide
|
|
52
|
+
-------------------------------------------------------------------------------
|
|
53
|
+
####### ######## ####### ####### ## ## ####
|
|
54
|
+
## ## ## ## ## ### ## ## ##
|
|
55
|
+
## ## ## ## ## #### ## ## ##
|
|
56
|
+
###### ## ###### ####### ## ## ## ## ##
|
|
57
|
+
## ## ## ## ## ## #### ########
|
|
58
|
+
## ## ## ## ## ## ### ## ##
|
|
59
|
+
####### ## ####### ## ## ## ## ## ##
|
|
60
|
+
|
|
61
|
+
Prof. Dr.-Ing. Hans-Georg Wenzel
|
|
62
|
+
Black Forest Observatory
|
|
63
|
+
Universitaet Karlsruhe
|
|
64
|
+
Englerstr. 7
|
|
65
|
+
D-76128 KARLSRUHE
|
|
66
|
+
Germany
|
|
67
|
+
Phone : ++49-0721-6082307
|
|
68
|
+
Telefax : ++49-0721-694552
|
|
69
|
+
e-mail : wenzel@gik.bau-verm.uni-karlsruhe.de
|
|
70
|
+
===============================================================================
|
|
71
|
+
This Python module was created based on the Fortran code PREDICT as part of
|
|
72
|
+
ETERNA 3.4, originally written by Prof. Hans George Wenzel in 1996. PREDICT is used to
|
|
73
|
+
calculate Earth tide gravity time series. The original PREDICT Fortran code was
|
|
74
|
+
updated to implement the new tidal catalogue by Kudryatvtsev (2004). The code
|
|
75
|
+
was then modernised (Fortran 90) for compilation as Python 3 module. This interface
|
|
76
|
+
provides a convenient way to utilise ETERNA PREDICT within Python.
|
|
77
|
+
|
|
78
|
+
The module relies on external files in the directory "commdat". The folowing files require
|
|
79
|
+
regular updating:
|
|
80
|
+
|
|
81
|
+
- etddt.dat - contains the difference between ephemeris time and UTC (include any leap seconds)
|
|
82
|
+
- etpolut.dat - contains the earth's pole rotation
|
|
83
|
+
|
|
84
|
+
The original Fortran code was also modified for use with f2py:
|
|
85
|
+
- COMMON blocks were transformed into modules
|
|
86
|
+
- continuous lines were updated for F90 compatibility
|
|
87
|
+
- the main program was changed into a subroutine (for f2py compliance)
|
|
88
|
+
- various other modernisations and enhancements
|
|
89
|
+
- BUG FIX: the original date and time data contained a rounding bug when the
|
|
90
|
+
sampling rate was lower than 60 seconds. This was successfully fixed.
|
|
91
|
+
===============================================================================
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
import pygtide.etpred as etpred
|
|
95
|
+
from datetime import datetime, timedelta, date
|
|
96
|
+
from warnings import warn
|
|
97
|
+
import numpy as np
|
|
98
|
+
import pandas as pd
|
|
99
|
+
from importlib import resources
|
|
100
|
+
import os
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class pygtide(object):
|
|
104
|
+
"""
|
|
105
|
+
The PyGTide class will initialise internal variables
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, msg=True):
|
|
109
|
+
"""
|
|
110
|
+
pygtide.init() initialises the etpred (Fortran) module and sets global variables
|
|
111
|
+
"""
|
|
112
|
+
self.msg = msg
|
|
113
|
+
self.fortran_version = etpred.inout.vers.astype(str)
|
|
114
|
+
# Resolve package data directory directly from filesystem.
|
|
115
|
+
# Avoid importlib.resources.as_file() which creates temp dirs that get cleaned up.
|
|
116
|
+
pkg_dir = os.path.dirname(__file__)
|
|
117
|
+
self.data_dir = os.path.join(pkg_dir, "commdat")
|
|
118
|
+
if not self.data_dir.endswith(os.sep):
|
|
119
|
+
self.data_dir += os.sep
|
|
120
|
+
|
|
121
|
+
# Fortran expects a fixed-length string (256 chars)
|
|
122
|
+
etpred.params.comdir = self.data_dir + " " * (256 - len(self.data_dir))
|
|
123
|
+
|
|
124
|
+
# OS-dependent null file
|
|
125
|
+
etpred.params.nullfile = os.devnull + " " * (10 - len(os.devnull))
|
|
126
|
+
|
|
127
|
+
self.args = []
|
|
128
|
+
|
|
129
|
+
# Initialise Fortran module
|
|
130
|
+
etpred.init()
|
|
131
|
+
|
|
132
|
+
# capture end date of file "etddt.dat" from module
|
|
133
|
+
year = int(etpred.inout.etd_start)
|
|
134
|
+
self.etddt_start = datetime(year, 1, 1)
|
|
135
|
+
year = etpred.inout.etd_end
|
|
136
|
+
self.etddt_end = datetime(int(year), 1, 1) + timedelta(
|
|
137
|
+
days=(year - int(year)) * 365
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# capture end date of file "etpolut1.dat" from module
|
|
141
|
+
self.etpolut1_start = datetime.strptime(str(etpred.inout.etpol_start), "%Y%m%d")
|
|
142
|
+
self.etpolut1_end = datetime.strptime(str(etpred.inout.etpol_end), "%Y%m%d")
|
|
143
|
+
|
|
144
|
+
self.headers = np.char.strip(etpred.inout.header.astype("str"))
|
|
145
|
+
# self.units = ['(m/s)**2','nm/s**2','mas','mm','mm','nstr','nstr','nstr','nstr','nstr','mm']
|
|
146
|
+
self.exec = False
|
|
147
|
+
|
|
148
|
+
self.wavegroup_def = np.asarray([[0, 10, 1, 0.0]])
|
|
149
|
+
self.set_wavegroup(self.wavegroup_def)
|
|
150
|
+
|
|
151
|
+
# %% sync the Python object
|
|
152
|
+
def update(self):
|
|
153
|
+
"""
|
|
154
|
+
self.update() refreshes the variables of PyGTide based on the Fortran module etpred
|
|
155
|
+
"""
|
|
156
|
+
self.headers = np.char.strip(etpred.inout.header.astype("str"))
|
|
157
|
+
self.args = etpred.inout.argsin
|
|
158
|
+
self.unit = etpred.inout.etpunit.astype("str")
|
|
159
|
+
|
|
160
|
+
# %% set wave group parameters
|
|
161
|
+
def set_wavegroup(self, wavedata=None):
|
|
162
|
+
if wavedata is None:
|
|
163
|
+
wavedata = self.wavegroup_def
|
|
164
|
+
# require at least 4 columns
|
|
165
|
+
if wavedata.shape[1] != 4:
|
|
166
|
+
raise ValueError("The wave group input must have 4 columns!")
|
|
167
|
+
return False
|
|
168
|
+
# require frequency ranges to increase and not overlap
|
|
169
|
+
freq_diffs = np.diff(wavedata[:, 0:1].flatten())
|
|
170
|
+
if (freq_diffs < 0).any():
|
|
171
|
+
raise ValueError(
|
|
172
|
+
"Wave group frequency ranges must be increasing and not overlapping!"
|
|
173
|
+
)
|
|
174
|
+
return False
|
|
175
|
+
if (wavedata[:, 2] < 0).any():
|
|
176
|
+
raise ValueError("Amplitude factors must be positive!")
|
|
177
|
+
return False
|
|
178
|
+
# set the wave group parameters
|
|
179
|
+
etpred.waves(
|
|
180
|
+
wavedata[:, 0],
|
|
181
|
+
wavedata[:, 1],
|
|
182
|
+
wavedata[:, 2],
|
|
183
|
+
wavedata[:, 3],
|
|
184
|
+
int(wavedata.shape[0]),
|
|
185
|
+
)
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
# %% reset the wave group
|
|
189
|
+
def reset_wavegroup(self):
|
|
190
|
+
self.set_wavegroup(self.wavegroup_def)
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
# run module etpred and return numbers
|
|
194
|
+
def predict(
|
|
195
|
+
self, latitude, longitude, height, startdate, duration, samprate, **control
|
|
196
|
+
):
|
|
197
|
+
"""
|
|
198
|
+
self.predict(latitude, longitude, height, startdate, duration, samprate, **control):
|
|
199
|
+
-------------------------------------------------------------------------------
|
|
200
|
+
Explanation of parameters used as numeric array "argsin". Parameters which are set
|
|
201
|
+
will overwrite default control parameters (ETERNA ini file input is disabled).
|
|
202
|
+
-------------------------------------------------------------------------------
|
|
203
|
+
Required parameters:
|
|
204
|
+
---
|
|
205
|
+
Latitude Ellipsoidal latitude of the station in degree referring to
|
|
206
|
+
WGS84 reference system (ETERNA: STATLATITU).
|
|
207
|
+
Longitude Ellipsoidal longitude of the station in degree referring
|
|
208
|
+
to WGS84 reference system (ETERNA: STATLONITU).
|
|
209
|
+
Height Ellipsoidal height of the station in meters referring to
|
|
210
|
+
WGS84 reference system (ETERNA: STATELEVAT).
|
|
211
|
+
Startdate Initial epoch, used to compute the Fourier development of
|
|
212
|
+
the specific earth tide component. Format is either string
|
|
213
|
+
'YYYY-MM-DD' or a datime object (ETERNA: INITIALEPO).
|
|
214
|
+
Duration Time span for the prediction in hours. The model tide series
|
|
215
|
+
will start at the initial epoch INITIALEPO and the time span
|
|
216
|
+
will be PREDICSPAN hours (ETERNA: PREDICSPAN).
|
|
217
|
+
Samprate Data sample interval in seconds (ETERNA: SAMPLERATE).
|
|
218
|
+
-------------------------------------------------------------------------------
|
|
219
|
+
Optional keyword (**control) parameters:
|
|
220
|
+
---
|
|
221
|
+
statgravit= Gravity of the station in m/s^2, necessary for tidal tilt
|
|
222
|
+
only. If the gravity is unknown, use a value of less than 1.0
|
|
223
|
+
and the program will compute and subsequently use the
|
|
224
|
+
normal gravity value referring to GRS80 reference system.
|
|
225
|
+
statazimut= azimuth of the instrument in degree decimal, reckoned
|
|
226
|
+
clockwise from north. This parameter is used for tidal tilt,
|
|
227
|
+
horizontal displacement and horizontal strain only.
|
|
228
|
+
tidalpoten= Parameter for the tidal potential catalog to be used:
|
|
229
|
+
1 = Doodson (1921) tidal potential catalog,
|
|
230
|
+
2 = Cartwright-Tayler-Edden (1973) tidal potential catalog
|
|
231
|
+
3 = Buellesfeld (1985) tidal potential catalog,
|
|
232
|
+
4 = Tamura (1987) tidal potential catalog,
|
|
233
|
+
5 = Xi (1989) tidal potential catalog,
|
|
234
|
+
6 = Roosbeek (1996) tidal potential catalog,
|
|
235
|
+
7 = Hartmann and Wenzel (1995) tidal potential catalog.
|
|
236
|
+
8 = (default) Kudryavtsev (2004) tidal potential catalog.
|
|
237
|
+
tidalcompo= Earth tide component:
|
|
238
|
+
= -1 for tidal potential (m**2/s**2)
|
|
239
|
+
= 0 (default) for tidal gravity in (nm/s**2)
|
|
240
|
+
= 1 for tidal tilt (mas), at azimuth STATAZIMUT.
|
|
241
|
+
= 2 for tidal vertical displacement (mm)
|
|
242
|
+
= 3 for tidal horizontal displacement (mm) azimuth STATAZIMUT.
|
|
243
|
+
= 4 for tidal vertical strain (10**-9 = nstr)
|
|
244
|
+
= 5 for tidal horizontal strain (10**-9 = nstr) azimuth STATAZIMUT.
|
|
245
|
+
= 6 for tidal areal strain (10**-9 = nstr)
|
|
246
|
+
= 7 for tidal shear strain (10**-9 = nstr)
|
|
247
|
+
= 8 for tidal volume strain (10**-9 = nstr)
|
|
248
|
+
The computed model tides will be given in the units defined above.
|
|
249
|
+
amtruncate= Amplitude threshold (default 0) for the tidal potential catalogue (m^2/s^2).
|
|
250
|
+
Only tidal waves with amplitudes exceeding the
|
|
251
|
+
amplitude threshold are used for the computation. This
|
|
252
|
+
reduces the execution time, but also the accuracy of the
|
|
253
|
+
computed tidal signales. For mean latitudes, the relation
|
|
254
|
+
between amplitude threshold and gravity tide accuracy is
|
|
255
|
+
for the Hartmann and Wenzel (1995) tidal potential catalog
|
|
256
|
+
threshold rms error [nm/s^2]
|
|
257
|
+
1.D-01 88.40
|
|
258
|
+
1.D-02 14.40
|
|
259
|
+
1.D-03 2.250
|
|
260
|
+
1.D-04 0.440
|
|
261
|
+
1.D-05 0.068
|
|
262
|
+
1.D-06 0.011
|
|
263
|
+
1.D-07 0.002
|
|
264
|
+
1.D-08 0.001
|
|
265
|
+
1.D-09 0.001
|
|
266
|
+
1.D-10 0.001
|
|
267
|
+
poltidecor= Amplitude factor for gravity pole tide. If the amplitude
|
|
268
|
+
factor is greater zero, gravity pole tides will be computed using
|
|
269
|
+
the IERS daily pole coordinates. Default value is 1.16.
|
|
270
|
+
lodtidecor= Amplitude factor for gravity LOD tide. If the amplitude
|
|
271
|
+
factor is greater zero, gravity LOD tides will be computed
|
|
272
|
+
using the IERS daily pole coordinates. Default value is 1.16.
|
|
273
|
+
fileout= Defaults value is 0 (output is suppressed). If set to 1, the routine
|
|
274
|
+
writes two text files called "self.inout.prd" and "self.inout.prn"
|
|
275
|
+
in the original format into the directory of the module.
|
|
276
|
+
screenout= Defaults value is 0 (output is silenced). If set to 1, the routine
|
|
277
|
+
writes output to the screen (but not the Python terminal).
|
|
278
|
+
-------------------------------------------------------------------------------
|
|
279
|
+
"""
|
|
280
|
+
# prepare full input argument array
|
|
281
|
+
argsin = np.zeros(18)
|
|
282
|
+
# define default values as given by the Fortran code
|
|
283
|
+
# tidal catalog
|
|
284
|
+
argsin[10] = 8
|
|
285
|
+
# amplitude truncation
|
|
286
|
+
argsin[12] = 1.0e-10
|
|
287
|
+
# values from: https://dx.doi.org/10.1016/j.jog.2005.08.035
|
|
288
|
+
argsin[13] = 1.16
|
|
289
|
+
argsin[14] = 1.16
|
|
290
|
+
|
|
291
|
+
# iterate through optional arguments passed
|
|
292
|
+
if "statgravit" in control:
|
|
293
|
+
if not (0 <= control["statgravit"] <= 20):
|
|
294
|
+
raise ValueError("Station gravity exceeds permissible range!")
|
|
295
|
+
else:
|
|
296
|
+
argsin[8] = control["statgravit"]
|
|
297
|
+
if "statazimut" in control:
|
|
298
|
+
if not (0 <= control["statazimut"] <= 180):
|
|
299
|
+
raise ValueError("Statazimut exceeds permissible range!")
|
|
300
|
+
else:
|
|
301
|
+
argsin[9] = control["statazimut"]
|
|
302
|
+
if "tidalpoten" in control:
|
|
303
|
+
if control["tidalpoten"] not in range(1, 9):
|
|
304
|
+
raise ValueError("Tidalpoten must be an integer between 1 and 8!")
|
|
305
|
+
else:
|
|
306
|
+
argsin[10] = control["tidalpoten"]
|
|
307
|
+
if "tidalcompo" in control:
|
|
308
|
+
if control["tidalcompo"] not in range(-1, 10):
|
|
309
|
+
raise ValueError("Tidalcompo must be an integer between -1 and 9!")
|
|
310
|
+
else:
|
|
311
|
+
argsin[11] = control["tidalcompo"]
|
|
312
|
+
if "amtruncate" in control:
|
|
313
|
+
if not (0 <= control["amtruncate"]):
|
|
314
|
+
raise ValueError("Amtruncate must be greater than 0!")
|
|
315
|
+
else:
|
|
316
|
+
argsin[12] = control["amtruncate"]
|
|
317
|
+
if "poltidecor" in control:
|
|
318
|
+
if not (control["poltidecor"] >= 0):
|
|
319
|
+
raise ValueError("Poltidecor must be >= 0!")
|
|
320
|
+
else:
|
|
321
|
+
argsin[13] = control["poltidecor"]
|
|
322
|
+
if "lodtidecor" in control:
|
|
323
|
+
if not (control["lodtidecor"] >= 0):
|
|
324
|
+
raise ValueError("Lodtidecor must be >= 0!")
|
|
325
|
+
else:
|
|
326
|
+
argsin[14] = control["lodtidecor"]
|
|
327
|
+
# additional control parameters
|
|
328
|
+
if "fileprd" in control:
|
|
329
|
+
if control["fileprd"] not in range(0, 2):
|
|
330
|
+
raise ValueError("Fileprd flag must be 0 or 1!")
|
|
331
|
+
else:
|
|
332
|
+
argsin[15] = control["fileprd"]
|
|
333
|
+
if "fileprn" in control:
|
|
334
|
+
if control["fileprn"] not in range(0, 2):
|
|
335
|
+
raise ValueError("Fileprn flag must be 0 or 1!")
|
|
336
|
+
else:
|
|
337
|
+
argsin[16] = control["fileprn"]
|
|
338
|
+
if "screenout" in control:
|
|
339
|
+
if control["screenout"] not in range(0, 2):
|
|
340
|
+
raise ValueError("Screenout flag must be 0 or 1!")
|
|
341
|
+
else:
|
|
342
|
+
argsin[17] = control["screenout"]
|
|
343
|
+
# process required parameters here
|
|
344
|
+
if not (-90 <= latitude <= 90):
|
|
345
|
+
raise ValueError("Latitude exceeds permissible range!")
|
|
346
|
+
else:
|
|
347
|
+
argsin[0] = latitude
|
|
348
|
+
if not (-180 <= longitude <= 180):
|
|
349
|
+
raise ValueError("Longitude exceeds permissible range!")
|
|
350
|
+
else:
|
|
351
|
+
argsin[1] = longitude
|
|
352
|
+
if not (-500 <= height <= 5000):
|
|
353
|
+
raise ValueError("Height exceeds permissible range!")
|
|
354
|
+
else:
|
|
355
|
+
argsin[2] = height
|
|
356
|
+
if not (0 < duration <= 10 * 24 * 365):
|
|
357
|
+
raise ValueError("Duration exceeds permissible range!")
|
|
358
|
+
else:
|
|
359
|
+
argsin[6] = int(duration)
|
|
360
|
+
|
|
361
|
+
# test startdate format and validity
|
|
362
|
+
if not (isinstance(startdate, date)):
|
|
363
|
+
try:
|
|
364
|
+
startdate = datetime.strptime(startdate, "%Y-%m-%d")
|
|
365
|
+
except ValueError:
|
|
366
|
+
raise ValueError("Startdate has incorrect format (YYYY-MM-DD)!")
|
|
367
|
+
enddate = startdate + timedelta(hours=duration)
|
|
368
|
+
# check if requested prediction series exceeds permissible time
|
|
369
|
+
if startdate < self.etddt_start:
|
|
370
|
+
fname = str(etpred.params.etddtdat)
|
|
371
|
+
warn(
|
|
372
|
+
"Prediction timeframe is earlier than the available time database (%s). "
|
|
373
|
+
"For details refer to the file '%s'." % (self.etddt_start, fname)
|
|
374
|
+
)
|
|
375
|
+
if enddate > (self.etddt_end + timedelta(days=365)):
|
|
376
|
+
fname = str(etpred.params.etddtdat)
|
|
377
|
+
warn(
|
|
378
|
+
"Please consider updating the leap second database '%s' (last value is from %s)."
|
|
379
|
+
% (fname, self.etddt_end)
|
|
380
|
+
)
|
|
381
|
+
# if not (-50*365 < (startdate - dt.datetime.now()).days < 365):
|
|
382
|
+
if ((argsin[13] > 0) or (argsin[14] > 0)) and (
|
|
383
|
+
(startdate < self.etpolut1_start) or (enddate > self.etpolut1_end)
|
|
384
|
+
):
|
|
385
|
+
fname = str(etpred.params.etddtdat)
|
|
386
|
+
warn(
|
|
387
|
+
"Dates exceed permissible range for pole/LOD tide correction (interval %s to %s). Consider update file '%s'."
|
|
388
|
+
% (self.etpolut1_start, self.etpolut1_end, fname)
|
|
389
|
+
)
|
|
390
|
+
if ((argsin[13] > 0) or (argsin[14] > 0)) and (
|
|
391
|
+
startdate < datetime.strptime("1600-01-01", "%Y-%m-%d")
|
|
392
|
+
):
|
|
393
|
+
raise ValueError(
|
|
394
|
+
"PyGTide should not be used for dates before the year 1600."
|
|
395
|
+
)
|
|
396
|
+
# set the start date and time
|
|
397
|
+
argsin[3:6] = [startdate.year, startdate.month, startdate.day]
|
|
398
|
+
# test sammprate validity
|
|
399
|
+
if not (0 < samprate <= 24 * 3600):
|
|
400
|
+
raise ValueError("samprate exceeds permissible range!")
|
|
401
|
+
else:
|
|
402
|
+
argsin[7] = int(samprate)
|
|
403
|
+
# test that samprate is not larger than duration
|
|
404
|
+
if samprate / 3600 > duration:
|
|
405
|
+
raise ValueError("samprate exceeds duration!")
|
|
406
|
+
# print(argsin)
|
|
407
|
+
self.args = argsin
|
|
408
|
+
if self.msg:
|
|
409
|
+
print("%s is calculating, please wait ..." % (self.fortran_version))
|
|
410
|
+
|
|
411
|
+
# hand over variables
|
|
412
|
+
etpred.predict(argsin)
|
|
413
|
+
|
|
414
|
+
self.exec = True
|
|
415
|
+
self.update()
|
|
416
|
+
return True
|
|
417
|
+
|
|
418
|
+
# easy access to the raw data calculated by the Fortran module
|
|
419
|
+
def results(self, digits=6):
|
|
420
|
+
"""
|
|
421
|
+
self.results(digits=6)
|
|
422
|
+
Returns:
|
|
423
|
+
- If predict() was executed, returns a dataframe with the results
|
|
424
|
+
- False
|
|
425
|
+
keyword 'digits' sets the number of digits returned.
|
|
426
|
+
"""
|
|
427
|
+
if self.exec:
|
|
428
|
+
# get the headers from Fortran
|
|
429
|
+
cols = np.char.strip(etpred.inout.header.astype("str"))
|
|
430
|
+
allcols = np.insert(cols[2:], 0, "UTC")
|
|
431
|
+
etdata = pd.DataFrame(columns=allcols)
|
|
432
|
+
# format date and time into padded number strings
|
|
433
|
+
etpred_data = np.array(etpred.inout.etpdata)
|
|
434
|
+
# print(etpred.inout.etpdata[:, 0])
|
|
435
|
+
# catch non-complete container fills from odd duration/sampling pairs
|
|
436
|
+
etpred_data = etpred_data[etpred_data[:, 0] > 0, :]
|
|
437
|
+
# convert
|
|
438
|
+
date = np.char.mod("%08.0f ", etpred_data[:, 0])
|
|
439
|
+
time = np.char.mod("%06.0f", etpred_data[:, 1])
|
|
440
|
+
# merge date and time arrays
|
|
441
|
+
datetime = np.core.defchararray.add(date, time)
|
|
442
|
+
etdata["UTC"] = pd.to_datetime(datetime, format="%Y%m%d %H%M%S", utc=True)
|
|
443
|
+
# obtain header strings from routine and convert
|
|
444
|
+
etdata[cols[2:]] = np.around(etpred_data[:, 2:], digits)
|
|
445
|
+
return etdata
|
|
446
|
+
else:
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
# easy access to the raw data calculated by Fortran
|
|
450
|
+
def raw(self):
|
|
451
|
+
"""
|
|
452
|
+
self.raw()
|
|
453
|
+
Returns:
|
|
454
|
+
- If predict() was executed, returns the raw data from the etpred module
|
|
455
|
+
- False
|
|
456
|
+
"""
|
|
457
|
+
if self.exec:
|
|
458
|
+
return etpred.inout.etpdata
|
|
459
|
+
else:
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
# easy access to the formatted data calculated by Fortran
|
|
463
|
+
def data(self, digits=6):
|
|
464
|
+
"""
|
|
465
|
+
self.data(digits=6):
|
|
466
|
+
Returns:
|
|
467
|
+
- If predict() was executed, returns a numpy array with the results
|
|
468
|
+
- False
|
|
469
|
+
keyword 'digits' sets the number of digits returned.
|
|
470
|
+
"""
|
|
471
|
+
if self.exec:
|
|
472
|
+
return np.around(etpred.inout.etpdata[:, 2:], digits)
|
|
473
|
+
else:
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
# easy access to the raw datetime calculated by Fortran
|
|
477
|
+
def datetime(self):
|
|
478
|
+
"""
|
|
479
|
+
self.datetime():
|
|
480
|
+
Returns:
|
|
481
|
+
- If predict() was executed, returns a numpy string array with the
|
|
482
|
+
calculated dates and times in seperate columns
|
|
483
|
+
- False
|
|
484
|
+
"""
|
|
485
|
+
if self.exec:
|
|
486
|
+
# reformat the date and time values obtained from ETERNA
|
|
487
|
+
date = np.char.mod("%08.0f", etpred.inout.etpdata[:, 0])
|
|
488
|
+
time = np.char.mod("%06.0f", etpred.inout.etpdata[:, 1])
|
|
489
|
+
return np.stack((date, time), axis=1)
|
|
490
|
+
else:
|
|
491
|
+
return None
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def predict_table(*args, msg=False, **kwargs):
|
|
495
|
+
kwargs.setdefault("screenout", int(msg))
|
|
496
|
+
pt = pygtide(msg=msg)
|
|
497
|
+
pt.predict(*args, **kwargs)
|
|
498
|
+
return pt.results()
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def predict_series(*args, msg=False, index=0, **kwargs):
|
|
502
|
+
kwargs.setdefault("screenout", int(msg))
|
|
503
|
+
pt = pygtide(msg=msg)
|
|
504
|
+
pt.predict(*args, **kwargs)
|
|
505
|
+
return pt.data()[:, index]
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def predict_spectrum(*args, nfft=None, **kwargs):
|
|
509
|
+
from numpy.fft import rfft, rfftfreq
|
|
510
|
+
|
|
511
|
+
sr = args[-1]
|
|
512
|
+
data = predict_series(*args, **kwargs)
|
|
513
|
+
if nfft is None:
|
|
514
|
+
nfft = len(data)
|
|
515
|
+
freq = rfftfreq(nfft, sr)
|
|
516
|
+
spec = rfft(data, nfft) * 2 / len(data)
|
|
517
|
+
return freq, spec
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def plot_series(*args, indices=(0, 1), show=True, **kwargs):
|
|
521
|
+
table = predict_table(*args, **kwargs)
|
|
522
|
+
table.plot(*indices)
|
|
523
|
+
if show:
|
|
524
|
+
import matplotlib.pyplot as plt
|
|
525
|
+
|
|
526
|
+
plt.show()
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def plot_spectrum(*args, ax=None, show=True, **kwargs):
|
|
530
|
+
import matplotlib.pyplot as plt
|
|
531
|
+
|
|
532
|
+
freq, spec = predict_spectrum(*args, **kwargs)
|
|
533
|
+
if ax is None:
|
|
534
|
+
ax = plt.subplot(111)
|
|
535
|
+
ax.plot(freq * 24 * 3600, np.abs(spec))
|
|
536
|
+
ax.set_xlabel("freq (cycles per day)")
|
|
537
|
+
ax.set_ylabel("amplitude")
|
|
538
|
+
if show:
|
|
539
|
+
plt.show()
|
pygtide/etpred.pyd
ADDED
|
Binary file
|
pygtide/tests.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# PyGTide tests
|
|
2
|
+
import numpy as np
|
|
3
|
+
from pygtide import predict_series, predict_spectrum, predict_table
|
|
4
|
+
from pygtide import plot_series, plot_spectrum
|
|
5
|
+
from pygtide import pygtide
|
|
6
|
+
|
|
7
|
+
def test(msg=False):
|
|
8
|
+
pt = pygtide(msg)
|
|
9
|
+
args = (-20.82071, -70.15288, 830.0, '2017-01-01', 6, 600)
|
|
10
|
+
pt.predict(*args, statazimut=90, tidalcompo=8)
|
|
11
|
+
pt.results()
|
|
12
|
+
|
|
13
|
+
# test against a proven array
|
|
14
|
+
args = (49.00937, 8.40444, 120, '2018-01-01', 50, 3600)
|
|
15
|
+
series = predict_series(*args, tidalcompo=5)
|
|
16
|
+
expected = np.array([ 20.120256, 11.371802, 0.303512, -10.684455, -19.316747,
|
|
17
|
+
-23.986328, -24.121086, -20.285591, -13.997906, -7.31219 ,
|
|
18
|
+
-2.274406, -0.387909, -2.221906, -7.260202, -14.028836,
|
|
19
|
+
-20.472805, -24.490089, -24.490442, -19.837274, -11.056935,
|
|
20
|
+
0.244288, 11.741719, 20.946235, 25.812105, 25.243209,
|
|
21
|
+
19.366281, 9.499723, -2.173853, -13.145977, -21.192355,
|
|
22
|
+
-24.899299, -23.975888, -19.27862 , -12.549307, -5.938311,
|
|
23
|
+
-1.436475, -0.358888, -3.008326, -8.60056 , -15.468955,
|
|
24
|
+
-21.497314, -24.673643, -23.626746, -18.009927, -8.631809,
|
|
25
|
+
2.704171, 13.61692 , 21.720306, 25.207537, 23.297815,
|
|
26
|
+
16.429417])
|
|
27
|
+
np.testing.assert_almost_equal(series, expected, 5)
|
|
28
|
+
|
|
29
|
+
args = (-20.82071, -70.15288, 830.0, '2020-01-01', 29.5 * 24, 600)
|
|
30
|
+
predict_table(*args, statazimut=90, tidalcompo=8, msg=msg)
|
|
31
|
+
freq, spec = predict_spectrum(*args, statazimut=90, tidalcompo=8)
|
|
32
|
+
index = np.argmax(np.abs(spec))
|
|
33
|
+
freqM2 = freq[index] * 3600
|
|
34
|
+
freqM2expected = 1 / 12.421
|
|
35
|
+
assert abs(freqM2 - freqM2expected) / freqM2expected < 2e-3
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import matplotlib.pyplot as plt
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
else:
|
|
42
|
+
plot_spectrum(*args, statazimut=90, tidalcompo=8, show=False)
|
|
43
|
+
plot_series(*args, statazimut=90, tidalcompo=8, show=False)
|
|
44
|
+
if msg:
|
|
45
|
+
plt.show()
|
|
46
|
+
|
|
47
|
+
print('------------------------------------')
|
|
48
|
+
print('Successfully finished PyGTide tests!')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def plot_pole_and_lod_tide():
|
|
52
|
+
try:
|
|
53
|
+
import matplotlib.pyplot as plt
|
|
54
|
+
except ImportError:
|
|
55
|
+
return False
|
|
56
|
+
ploty = ['Signal [nm/s**2]', 'Pole tide [nm/s**2]', 'LOD tide [nm/s**2]']
|
|
57
|
+
dates = ['1961-12-24', '2016-12-24', '2023-06-01']
|
|
58
|
+
for date in dates:
|
|
59
|
+
args = (20, 0, 0, date, 24*30, 600)
|
|
60
|
+
tab = predict_table(*args)
|
|
61
|
+
tab.plot(x='UTC', y=ploty, sharex=True, subplots=True)
|
|
62
|
+
plt.savefig(f'tides_{date}.png')
|
|
63
|
+
args = (-20.82071, -70.15288, 830.0, '2017-01-01', 10, 600)
|
|
64
|
+
tab = predict_table(*args, statazimut=90)
|
|
65
|
+
tab.plot(x='UTC', y=ploty, sharex=True, subplots=True)
|
|
66
|
+
plt.savefig('tides_2017-01-01_10h.png')
|
|
67
|
+
plt.show()
|
|
68
|
+
|
|
69
|
+
# test()
|
|
70
|
+
#_plot_tides()
|