wavespectra 4.0.2__tar.gz → 4.1.0__tar.gz
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.
- {wavespectra-4.0.2/wavespectra.egg-info → wavespectra-4.1.0}/PKG-INFO +5 -1
- {wavespectra-4.0.2 → wavespectra-4.1.0}/README.rst +4 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/__init__.py +1 -1
- wavespectra-4.1.0/wavespectra/input/awac.py +496 -0
- wavespectra-4.1.0/wavespectra/input/datawell.py +178 -0
- wavespectra-4.1.0/wavespectra/input/spotter.py +294 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/specpart/specpartc.c +1 -1
- {wavespectra-4.0.2 → wavespectra-4.1.0/wavespectra.egg-info}/PKG-INFO +5 -1
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra.egg-info/SOURCES.txt +2 -0
- wavespectra-4.0.2/wavespectra/input/spotter.py +0 -478
- {wavespectra-4.0.2 → wavespectra-4.1.0}/AUTHORS.rst +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/LICENSE.txt +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/pyproject.toml +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/setup.cfg +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/setup.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/tests/test_cli.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/tests/test_cookiecutter_template.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/tests/test_partition.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/tests/test_specarray.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/cli.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/construct/__init__.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/construct/direction.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/construct/frequency.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/__init__.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/attributes.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/attributes.yml +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/fitting.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/npstats.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/select.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/swan.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/utils.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/core/xrstats.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/__init__.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/dataset.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/era5.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/funwave.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/json.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/ncswan.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/ndbc.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/ndbc_ascii.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/netcdf.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/obscape.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/octopus.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/swan.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/triaxys.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/wavespectra.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/ww3.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/ww3_station.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/wwm.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/input/xwaves.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/__init__.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/funwave.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/json.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/netcdf.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/octopus.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/orcaflex.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/swan.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/ww3.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/output/ww3.yml +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/__init__.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/hanson_and_phillips_2001.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/partition.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/specpart/specpartc.h +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/specpart/specpartc_wrap.c +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/partition/tracking.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/plot.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/specarray.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra/specdataset.py +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra.egg-info/dependency_links.txt +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra.egg-info/entry_points.txt +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra.egg-info/requires.txt +0 -0
- {wavespectra-4.0.2 → wavespectra-4.1.0}/wavespectra.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wavespectra
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Library for ocean wave spectra
|
|
5
5
|
Author-email: Wavespectra Developers <r.guedes@oceanum.science>
|
|
6
6
|
Maintainer-email: Rafael Guedes <r.guedes@oceanum.science>, Tom Durrant <t.durrant@oceanum.science>
|
|
@@ -95,6 +95,10 @@ Python library for ocean wave spectra.
|
|
|
95
95
|
.. image:: https://img.shields.io/pypi/dm/wavespectra
|
|
96
96
|
:target: https://pypistats.org/packages/wavespectra
|
|
97
97
|
:alt: PyPI - Downloads
|
|
98
|
+
.. image:: https://anaconda.org/conda-forge/wavespectra/badges/version.svg
|
|
99
|
+
:target: https://anaconda.org/conda-forge/wavespectra
|
|
100
|
+
.. image:: https://anaconda.org/conda-forge/wavespectra/badges/platforms.svg
|
|
101
|
+
:target: https://anaconda.org/conda-forge/wavespectra
|
|
98
102
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
99
103
|
:target: https://github.com/python/black
|
|
100
104
|
.. image:: https://img.shields.io/pypi/pyversions/wavespectra
|
|
@@ -16,6 +16,10 @@ Python library for ocean wave spectra.
|
|
|
16
16
|
.. image:: https://img.shields.io/pypi/dm/wavespectra
|
|
17
17
|
:target: https://pypistats.org/packages/wavespectra
|
|
18
18
|
:alt: PyPI - Downloads
|
|
19
|
+
.. image:: https://anaconda.org/conda-forge/wavespectra/badges/version.svg
|
|
20
|
+
:target: https://anaconda.org/conda-forge/wavespectra
|
|
21
|
+
.. image:: https://anaconda.org/conda-forge/wavespectra/badges/platforms.svg
|
|
22
|
+
:target: https://anaconda.org/conda-forge/wavespectra
|
|
19
23
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
20
24
|
:target: https://github.com/python/black
|
|
21
25
|
.. image:: https://img.shields.io/pypi/pyversions/wavespectra
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""Reader for AWAC spectra from NMEA data
|
|
2
|
+
|
|
3
|
+
https://www.nortekgroup.com/
|
|
4
|
+
|
|
5
|
+
The spectra are reconstructed from the Fourier coefficients using the maximum entropy method to remove negative energy.
|
|
6
|
+
Normalization is applied per frequency to keep energy over directions identical to energy in non-directional spectrum
|
|
7
|
+
The 2D spectral data is scaled to the significant wave height (Hs) given in the wave parameters.
|
|
8
|
+
|
|
9
|
+
The time is returned as read from the timestamps. The time-zone depends on the configuration of the device and is not included in the output.
|
|
10
|
+
|
|
11
|
+
revision history:
|
|
12
|
+
-----------------
|
|
13
|
+
2024-08-15 : First version - Ruben de Bruin
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Data description and example data:
|
|
17
|
+
-------------
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Fourier coeffcients:
|
|
21
|
+
|
|
22
|
+
Field Description Form
|
|
23
|
+
0 Identifier “$PNORF”
|
|
24
|
+
1 Fourier coefficient flag (A1/B1/A2/B2) "CC"
|
|
25
|
+
2 Date MMDDYY
|
|
26
|
+
3 Time hhmmss.s
|
|
27
|
+
4 Spectrum basis type (0=pressure, 1=velocity,
|
|
28
|
+
3=AST) n
|
|
29
|
+
5 Start Frequency (Hz) d.dd
|
|
30
|
+
6 Step Frequency (Hz) d.dd
|
|
31
|
+
7 Number of Frequencies N nnn
|
|
32
|
+
8 Fourier Coefficient CC [frequency 1] d.dddd
|
|
33
|
+
9 Fourier Coefficient CC [frequency 2] d.dddd
|
|
34
|
+
N+7 Fourier Coefficient CC [frequency N] d.dddd
|
|
35
|
+
N+8 Checksum (hex) *hh
|
|
36
|
+
|
|
37
|
+
$PNORF,A1,050120,000101,3,0.02,0.01,48,-0.1370,-0.1413,-0.2548,-0.7437,-0.9243,-0.9156,-0.9092,-0.9027,-0.8771,-0.8651,-0.8662,-0.8520,-0.8440,-0.8249,-0.8166,-0.8455,-0.8518,-0.8404,-0.8077,-0.7297,-0.6421,-0.5878,-0.5803,-0.5477,-0.5559,-0.5445,-0.3028,-0.2844,-0.1277,-0.2942,-0.2004,-0.1690,-0.1242,-0.0984,-0.0428,-0.0352,-0.0103,-0.0138,0.0057,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000*06
|
|
38
|
+
$PNORF,B1,050120,000101,3,0.02,0.01,48,0.2317,0.2715,0.2530,0.2815,0.3104,0.3728,0.3932,0.3831,0.3758,0.3861,0.3942,0.4068,0.3551,0.3375,0.2897,0.2033,0.1489,0.0765,0.1139,0.1978,0.2613,0.1794,0.2080,0.0510,0.0194,-0.0606,0.0258,0.1475,0.1210,0.0400,0.0183,-0.0247,0.0287,0.0026,-0.0161,-0.0041,0.0134,0.0183,0.0113,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000*05
|
|
39
|
+
$PNORF,A2,050120,000101,3,0.02,0.01,48,0.0298,-0.0182,0.0292,0.5327,0.7465,0.6843,0.6601,0.6511,0.5770,0.5368,0.5383,0.5018,0.5014,0.4659,0.4671,0.5400,0.5509,0.5443,0.4952,0.3610,0.2616,0.2780,0.3188,0.2016,0.1471,0.2066,0.1387,0.0577,0.0479,0.0829,0.0135,0.0651,-0.0119,0.0106,-0.0576,-0.0748,-0.0762,-0.0359,-0.0048,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000*2B
|
|
40
|
+
$PNORF,B2,050120,000101,3,0.02,0.01,48,-0.0619,-0.0377,-0.0514,-0.3674,-0.5502,-0.6668,-0.7015,-0.6633,-0.6189,-0.6227,-0.6373,-0.6422,-0.5306,-0.4705,-0.3596,-0.2469,-0.1640,-0.0557,-0.0666,-0.1313,-0.1363,-0.0540,-0.0295,0.0234,0.0481,0.0893,0.0843,-0.0011,0.0024,0.0494,0.1149,0.1554,0.0620,-0.0601,-0.0865,-0.0371,-0.0058,-0.0191,-0.0185,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000,-9.0000*0C
|
|
41
|
+
|
|
42
|
+
Spectra:
|
|
43
|
+
|
|
44
|
+
Field Description Form
|
|
45
|
+
0 Identifier “$PNORE”
|
|
46
|
+
1 Date MMDDYY
|
|
47
|
+
2 Time hhmmss.s
|
|
48
|
+
3 Spectrum basis type (0=pressure, 1=velocity,3=AST) n
|
|
49
|
+
4 Start Frequency (Hz) d.dd
|
|
50
|
+
5 Step Frequency (Hz) d.dd
|
|
51
|
+
6 Number of Frequencies N nnn
|
|
52
|
+
7 Energy Density [frequency 1] (cm2/Hz) dddd.ddd
|
|
53
|
+
8 Energy Density [frequency 2] (cm2/Hz) dddd.ddd
|
|
54
|
+
N+6 Energy Density [frequency N] (cm2/Hz) dddd.ddd
|
|
55
|
+
|
|
56
|
+
$PNORE,050120,000101,3,0.02,0.01,98,0.063,0.029,0.020,0.182,2.671,5.169,6.311,4.587,2.876,1.802,1.817,1.
|
|
57
|
+
442,1.163,0.878,0.653,0.575,0.549,0.409,0.306,0.195,0.184,0.167,0.159,0.119,0.105,0.085,0.072,0.052,0.053,0.045,0.034,0.025,0.022,0.018,0.018,0.018,0.014,0.011,0.008,0.008,0.008,0.007,0.006,0.006,0.005,0.005,0.005,0.005,0.005,0.004,0.003,0.003,0.002,0.003,0.002,0.002,0.002,0.001,0.002,0.002,0.002,0.002,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.001,0.000,0.000,0.000,0.000,0.000,0.000*75
|
|
58
|
+
|
|
59
|
+
Wave Parameters:
|
|
60
|
+
|
|
61
|
+
Field Description Form
|
|
62
|
+
0 Identifier “$PNORW”
|
|
63
|
+
1 Date MMDDYY
|
|
64
|
+
2 Time hhmmss
|
|
65
|
+
3 Spectrum basis type (0=pressure, 1=velocity,3=AST)
|
|
66
|
+
4 Processing method (1=PUV, 2=SUV, 3=MLM,4=MLMST)
|
|
67
|
+
5 Hm0 (m) dd.dd
|
|
68
|
+
6 H3 (m) dd.dd
|
|
69
|
+
7 H10 (m) dd.dd
|
|
70
|
+
8 Hmax (m) dd.dd
|
|
71
|
+
9 Tm02 (s) dd.dd
|
|
72
|
+
10 Tp (s) dd.dd
|
|
73
|
+
11 Tz (s) dd.dd
|
|
74
|
+
12 DirTp (deg) ddd.dd
|
|
75
|
+
13 SprTp (deg) ddd.dd
|
|
76
|
+
14 Main Direction (deg) ddd.dd
|
|
77
|
+
15 Unidirectivity Index dd.dd
|
|
78
|
+
16 Mean pressure (dbar) dd.dd
|
|
79
|
+
17 Number of no detects n
|
|
80
|
+
18 Number of bad detects n
|
|
81
|
+
19 Near surface Current speed (m/s) dd.dd
|
|
82
|
+
20 Near surface Current direction (deg) ddd.dd
|
|
83
|
+
21 Error Code hhhh
|
|
84
|
+
22 Checksum (hex) *hh
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
$PNORW,073010,051001,3,4,0.55,0.51,0.63,0.82,2.76,3.33,2.97,55.06,78.91,337.62,0.48,22.35,0,1,0.
|
|
88
|
+
27,129.11,0000*4E
|
|
89
|
+
|
|
90
|
+
Reconstructing a spectrum from these coefficients
|
|
91
|
+
--------------------------------------------------
|
|
92
|
+
|
|
93
|
+
DirRes = 4;
|
|
94
|
+
freq = 0.02:0.01:(48*0.01 + 0.01);
|
|
95
|
+
dDir = 0:pi*DirRes/180:(2*pi-pi*DirRes/180);
|
|
96
|
+
Spec = S;
|
|
97
|
+
|
|
98
|
+
% (conversion from Cart to Compass)
|
|
99
|
+
A1temp = A1;
|
|
100
|
+
B1temp= B1;
|
|
101
|
+
A1 = -B1temp;
|
|
102
|
+
B1= -A1temp;
|
|
103
|
+
A2 = -A2;
|
|
104
|
+
B2 = B2;
|
|
105
|
+
|
|
106
|
+
% Direction From
|
|
107
|
+
dr = pi;
|
|
108
|
+
A1new = cos(dr)*A1 + sin(dr)*B1;
|
|
109
|
+
B1new = -sin(dr)*A1 + cos(dr)*B1;
|
|
110
|
+
A2new = cos(2*dr)*A2 + sin(2*dr)*B2;
|
|
111
|
+
B2new = -sin(2*dr)*A2 + cos(2*dr)*B2;
|
|
112
|
+
|
|
113
|
+
% Now apply maximum entropy method to remove negative energy
|
|
114
|
+
for j=1:length(freq)
|
|
115
|
+
|
|
116
|
+
% This little gem switches from cart2compass
|
|
117
|
+
for dd = 1:length(dDir)
|
|
118
|
+
dNewdir = pi/2 - dDir(dd);
|
|
119
|
+
|
|
120
|
+
%Pure Fourier coef soln w/o MEM
|
|
121
|
+
DirFieldOLD(j,dd) = (A1new(j)*cos(dNewdir) + B1new(j)*sin(dNewdir) + A2new(j)*cos(2*dNewdir) + B2new(j)*sin(2*dNewdir))*Spec(ii,j);
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
--OR--
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
% Now apply maximum entropy method to remove negative energy
|
|
129
|
+
for j=1:length(freq)
|
|
130
|
+
c1 = A1new(j) + 1i*B1new(j);
|
|
131
|
+
c2 = A2new(j) + 1i*B2new(j);
|
|
132
|
+
%c1 = A1new(ii,j) + i*B1new(ii,j);
|
|
133
|
+
%c2 = A2new(ii,j) + i*B2new(ii,j);
|
|
134
|
+
|
|
135
|
+
fi1 = (c1 - c2*conj(c1)) / (1 - abs(c1)*abs(c1));
|
|
136
|
+
fi2 = c2 - c1*fi1;
|
|
137
|
+
|
|
138
|
+
% This little gem switches from cart2compass
|
|
139
|
+
for dd = 1:length(dDir)
|
|
140
|
+
dNewdir = pi/2 - dDir(dd);
|
|
141
|
+
%dNewdir = dDir(dd);
|
|
142
|
+
|
|
143
|
+
numer = 1 - fi1*conj(c1) - fi2*conj(c2);
|
|
144
|
+
%denom = 1 - fi1*exp(-i*dDir(dd)) - fi2*exp(-2.0*i*dDir(dd));
|
|
145
|
+
denom = 1 - fi1*exp(-1i*dNewdir) - fi2*exp(-2.0*1i*dNewdir);
|
|
146
|
+
DirField(j,dd) = real(numer/(abs(denom)*abs(denom)))/(2.0*pi)*Spec(ii,j)*pi*DirRes/180;
|
|
147
|
+
|
|
148
|
+
%Pure Fourier coef soln w/o ME
|
|
149
|
+
DirFieldOLD(j,dd) = (A1new(j)*cos(dNewdir) + B1new(j)*sin(dNewdir) + A2new(j)*cos(2*dNewdir) + B2new(j)*sin(2*dNewdir))*Spec(ii,j);
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
import datetime
|
|
157
|
+
import warnings
|
|
158
|
+
|
|
159
|
+
import numpy as np
|
|
160
|
+
|
|
161
|
+
import xarray as xr
|
|
162
|
+
from wavespectra.core.attributes import set_spec_attributes
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def read_awac(filename):
|
|
166
|
+
"""Read Spectra from Nortec AWAC NMEA file.
|
|
167
|
+
|
|
168
|
+
The spectra are reconstructed from the Fourier coefficients using the maximum
|
|
169
|
+
entropy method to remove negative energy. Normalisation is applied per frequency to
|
|
170
|
+
keep energy over directions identical to energy in non-directional spectrum. The 2D
|
|
171
|
+
spectral data is scaled to the significant wave height (Hs) given in the wave
|
|
172
|
+
parameters.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
- filename (str): path to AWAC file.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
- dset (SpecDataset): spectra dataset object read from file.
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
with open(filename, "r") as f:
|
|
182
|
+
lines = [line for line in f.read().split("\n")]
|
|
183
|
+
return read_awac_strings(lines)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def parse_awac_nmnea_wave_parameters(lines):
|
|
187
|
+
for line in lines:
|
|
188
|
+
if line.startswith("$PNORW"):
|
|
189
|
+
blocks = line.split(",")
|
|
190
|
+
timestamp = blocks[1] + blocks[2]
|
|
191
|
+
Hm0 = float(blocks[5])
|
|
192
|
+
H3 = float(blocks[6])
|
|
193
|
+
H10 = float(blocks[7])
|
|
194
|
+
Hmax = float(blocks[8])
|
|
195
|
+
Tm02 = float(blocks[9])
|
|
196
|
+
Tp = float(blocks[10])
|
|
197
|
+
Tz = float(blocks[11])
|
|
198
|
+
DirTp = float(blocks[12])
|
|
199
|
+
SprTp = float(blocks[13])
|
|
200
|
+
MainDir = float(blocks[14])
|
|
201
|
+
UnidirectivityIndex = float(blocks[15])
|
|
202
|
+
MeanPressure = float(blocks[16])
|
|
203
|
+
NearSurfaceCurrentSpeed = float(blocks[19])
|
|
204
|
+
NearSurfaceCurrentDirection = float(blocks[20])
|
|
205
|
+
|
|
206
|
+
# yield as dict
|
|
207
|
+
yield {
|
|
208
|
+
"timestamp": timestamp,
|
|
209
|
+
"Hm0": Hm0,
|
|
210
|
+
"H3": H3,
|
|
211
|
+
"H10": H10,
|
|
212
|
+
"Hmax": Hmax,
|
|
213
|
+
"Tm02": Tm02,
|
|
214
|
+
"Tp": Tp,
|
|
215
|
+
"Tz": Tz,
|
|
216
|
+
"DirTp": DirTp,
|
|
217
|
+
"SprTp": SprTp,
|
|
218
|
+
"MainDir": MainDir,
|
|
219
|
+
"UnidirectivityIndex": UnidirectivityIndex,
|
|
220
|
+
"MeanPressure": MeanPressure,
|
|
221
|
+
"NearSurfaceCurrentSpeed": NearSurfaceCurrentSpeed,
|
|
222
|
+
"NearSurfaceCurrentDirection": NearSurfaceCurrentDirection,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def parse_awac_nmea(lines):
|
|
227
|
+
|
|
228
|
+
# scan to find frequency grid
|
|
229
|
+
freq = None
|
|
230
|
+
|
|
231
|
+
# read spectral frequencies from PNORF
|
|
232
|
+
for line in lines:
|
|
233
|
+
if line.startswith("$PNORF"):
|
|
234
|
+
blocks = line.split(",")
|
|
235
|
+
fstart = float(blocks[5])
|
|
236
|
+
fstep = float(blocks[6])
|
|
237
|
+
fn = int(blocks[7])
|
|
238
|
+
|
|
239
|
+
freq = fstart + np.linspace(start=0, stop=fn * fstep, num=fn, endpoint=True)
|
|
240
|
+
|
|
241
|
+
break
|
|
242
|
+
|
|
243
|
+
if freq is None:
|
|
244
|
+
raise ValueError("Can not determine frequency grid, $PNORF field not found")
|
|
245
|
+
|
|
246
|
+
# read spectral frequencies from PNORE
|
|
247
|
+
ok = False
|
|
248
|
+
for line in lines:
|
|
249
|
+
if line.startswith("$PNORE"):
|
|
250
|
+
blocks = line.split(",")
|
|
251
|
+
fstart2 = float(blocks[4])
|
|
252
|
+
fstep2 = float(blocks[5])
|
|
253
|
+
|
|
254
|
+
# assert that the frequency grid is the same
|
|
255
|
+
assert (
|
|
256
|
+
fstart == fstart2
|
|
257
|
+
), "Frequency start does not match between $PNORF and $PNORE"
|
|
258
|
+
assert (
|
|
259
|
+
fstep == fstep2
|
|
260
|
+
), "Frequency step does not match between $PNORF and $PNORE"
|
|
261
|
+
|
|
262
|
+
ok = True
|
|
263
|
+
break
|
|
264
|
+
if not ok:
|
|
265
|
+
raise ValueError("Can not determine frequency grid, $PNORE field not found")
|
|
266
|
+
|
|
267
|
+
A1 = None
|
|
268
|
+
A2 = None
|
|
269
|
+
B1 = None
|
|
270
|
+
B2 = None
|
|
271
|
+
S = None
|
|
272
|
+
Hs = None
|
|
273
|
+
timestamp = None
|
|
274
|
+
|
|
275
|
+
for line in lines:
|
|
276
|
+
line = line.split("*")[0]
|
|
277
|
+
|
|
278
|
+
# new timestamp?
|
|
279
|
+
TS = None
|
|
280
|
+
if line.startswith("$PNORF"):
|
|
281
|
+
TS = line[10:16] + line[17:23]
|
|
282
|
+
elif line.startswith("$PNORE"):
|
|
283
|
+
TS = line[7:13] + line[14:20]
|
|
284
|
+
elif line.startswith("$PNORW"):
|
|
285
|
+
blocks = line.split(",")
|
|
286
|
+
TS = blocks[1] + blocks[2]
|
|
287
|
+
|
|
288
|
+
if TS is not None:
|
|
289
|
+
if TS != timestamp:
|
|
290
|
+
if A1 is not None:
|
|
291
|
+
yield timestamp, freq, A1, A2, B1, B2, S, Hs
|
|
292
|
+
|
|
293
|
+
timestamp = TS
|
|
294
|
+
A1 = None
|
|
295
|
+
A2 = None
|
|
296
|
+
B1 = None
|
|
297
|
+
B2 = None
|
|
298
|
+
S = None
|
|
299
|
+
Hs = None
|
|
300
|
+
|
|
301
|
+
if line.startswith("$PNORF"):
|
|
302
|
+
# remove the checksum
|
|
303
|
+
blocks = line.split(",")
|
|
304
|
+
if blocks[1] == "A1":
|
|
305
|
+
if A1 is not None:
|
|
306
|
+
warnings.warn(
|
|
307
|
+
f"Duplicate A1 spectrum found for timestamp {timestamp}"
|
|
308
|
+
)
|
|
309
|
+
A1 = np.array([float(x) for x in blocks[8:]])
|
|
310
|
+
elif blocks[1] == "A2":
|
|
311
|
+
if A2 is not None:
|
|
312
|
+
warnings.warn(
|
|
313
|
+
f"Duplicate A2 spectrum found for timestamp {timestamp}"
|
|
314
|
+
)
|
|
315
|
+
A2 = np.array([float(x) for x in blocks[8:]])
|
|
316
|
+
elif blocks[1] == "B1":
|
|
317
|
+
if B1 is not None:
|
|
318
|
+
warnings.warn(
|
|
319
|
+
f"Duplicate B1 spectrum found for timestamp {timestamp}"
|
|
320
|
+
)
|
|
321
|
+
B1 = np.array([float(x) for x in blocks[8:]])
|
|
322
|
+
elif blocks[1] == "B2":
|
|
323
|
+
if B2 is not None:
|
|
324
|
+
warnings.warn(
|
|
325
|
+
f"Duplicate B2 spectrum found for timestamp {timestamp}"
|
|
326
|
+
)
|
|
327
|
+
B2 = np.array([float(x) for x in blocks[8:]])
|
|
328
|
+
elif line.startswith("$PNORE"):
|
|
329
|
+
blocks = line.split(",")
|
|
330
|
+
n = int(blocks[6])
|
|
331
|
+
S = np.array([float(x) for x in blocks[7:]])
|
|
332
|
+
if len(S) != n:
|
|
333
|
+
warnings.warn(
|
|
334
|
+
f"Number of frequencies in spectrum does not match number of frequencies expected {n} got {len(S)}"
|
|
335
|
+
)
|
|
336
|
+
elif line.startswith("$PNORW"):
|
|
337
|
+
blocks = line.split(",")
|
|
338
|
+
Hs = float(blocks[5])
|
|
339
|
+
|
|
340
|
+
if A1 is not None:
|
|
341
|
+
yield timestamp, freq, A1, A2, B1, B2, S, Hs
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def read_awac_strings(
|
|
345
|
+
lines,
|
|
346
|
+
nDirs=90,
|
|
347
|
+
):
|
|
348
|
+
"""Parses AWAC NMEA data into directional spectra."""
|
|
349
|
+
|
|
350
|
+
data = [R for R in parse_awac_nmea(lines)]
|
|
351
|
+
|
|
352
|
+
# each line of data contains a timestamp, freq, A1, A2, B1, B2, S
|
|
353
|
+
# the first and last line may contain Nones for A1, A2, B1, B2, S if the file was incomplete. In that case we remove them
|
|
354
|
+
|
|
355
|
+
if any(x is None for x in data[0]):
|
|
356
|
+
warnings.warn(
|
|
357
|
+
f"First spectrum of AWAC data incomplete, removed spectrum with timestamp {data[0][0]}"
|
|
358
|
+
)
|
|
359
|
+
data = data[1:]
|
|
360
|
+
if any(x is None for x in data[-1]):
|
|
361
|
+
warnings.warn(
|
|
362
|
+
f"Last spectrum of AWAC data incomplete, removed with timestamp {data[0][0]}"
|
|
363
|
+
)
|
|
364
|
+
data = data[:-1]
|
|
365
|
+
|
|
366
|
+
# convert to directional spectra
|
|
367
|
+
dDir = np.linspace(0, 2 * np.pi, num=nDirs, endpoint=False)
|
|
368
|
+
dir_step = dDir[1] - dDir[0]
|
|
369
|
+
|
|
370
|
+
efth = []
|
|
371
|
+
times = []
|
|
372
|
+
|
|
373
|
+
for timestamp, freq, aA1, aA2, aB1, aB2, S, Hs in data:
|
|
374
|
+
# %(conversion from Cart to Compass)
|
|
375
|
+
# A1temp = A1.copy()
|
|
376
|
+
# B1temp = B1.copy()
|
|
377
|
+
#
|
|
378
|
+
# print('A1 = [' + ','.join([str(a) for a in aA1]) + ']')
|
|
379
|
+
# print('A2 = [' + ','.join([str(a) for a in aA2]) + ']')
|
|
380
|
+
# print('B1 = [' + ','.join([str(a) for a in aB1]) + ']')
|
|
381
|
+
# print('B2 = [' + ','.join([str(a) for a in aB2]) + ']')
|
|
382
|
+
# print('S = [' + ','.join([str(a) for a in S]) + ']')
|
|
383
|
+
|
|
384
|
+
A1 = -aB1.copy()
|
|
385
|
+
B1 = -aA1.copy()
|
|
386
|
+
A2 = -aA2.copy()
|
|
387
|
+
B2 = aB2.copy()
|
|
388
|
+
|
|
389
|
+
# Direction From
|
|
390
|
+
dr = np.pi
|
|
391
|
+
A1new = np.cos(dr) * A1 + np.sin(dr) * B1
|
|
392
|
+
B1new = -np.sin(dr) * A1 + np.cos(dr) * B1
|
|
393
|
+
A2new = np.cos(2 * dr) * A2 + np.sin(2 * dr) * B2
|
|
394
|
+
B2new = -np.sin(2 * dr) * A2 + np.cos(2 * dr) * B2
|
|
395
|
+
|
|
396
|
+
# Now apply maximum entropy method to remove negative energy
|
|
397
|
+
# Initialize the directional field
|
|
398
|
+
DirField = np.zeros((len(freq), len(dDir)))
|
|
399
|
+
|
|
400
|
+
# Apply maximum entropy method to remove negative energy
|
|
401
|
+
for iFreq in range(len(freq)):
|
|
402
|
+
|
|
403
|
+
c1 = A1new[iFreq] + 1j * B1new[iFreq]
|
|
404
|
+
c2 = A2new[iFreq] + 1j * B2new[iFreq]
|
|
405
|
+
|
|
406
|
+
fi1 = (c1 - c2 * np.conj(c1)) / (1 - np.abs(c1) * np.abs(c1))
|
|
407
|
+
fi2 = c2 - c1 * fi1
|
|
408
|
+
|
|
409
|
+
# print(f"c1 = {c1}")
|
|
410
|
+
# print(f"c2 = {c2}")
|
|
411
|
+
# print(f"fi1 = {fi1}")
|
|
412
|
+
# print(f"fi2 = {fi2}")
|
|
413
|
+
|
|
414
|
+
factors = []
|
|
415
|
+
for dd in range(len(dDir)):
|
|
416
|
+
|
|
417
|
+
dNewdir = np.pi / 2 - dDir[dd]
|
|
418
|
+
numer = 1 - fi1 * np.conj(c1) - fi2 * np.conj(c2)
|
|
419
|
+
denom = (
|
|
420
|
+
1 - fi1 * np.exp(-1j * dNewdir) - fi2 * np.exp(-2.0 * 1j * dNewdir)
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
dir_factor = np.real(numer / (np.abs(denom) * np.abs(denom)))
|
|
424
|
+
|
|
425
|
+
scale1 = 1 / (2 * np.pi)
|
|
426
|
+
scale2 = dir_step
|
|
427
|
+
|
|
428
|
+
scale = scale1 * scale2
|
|
429
|
+
|
|
430
|
+
factor = scale * dir_factor
|
|
431
|
+
|
|
432
|
+
DirField[iFreq, dd] = factor * S[iFreq]
|
|
433
|
+
#
|
|
434
|
+
# print("======================")
|
|
435
|
+
# print(f"dd = {dd + 1}")
|
|
436
|
+
# print(f"dNewdir = {dNewdir}")
|
|
437
|
+
# print(f"numer = {numer}")
|
|
438
|
+
# print(f"denom = {denom}")
|
|
439
|
+
# print(f"dir_factor = {dir_factor}")
|
|
440
|
+
|
|
441
|
+
factors.append(factor)
|
|
442
|
+
|
|
443
|
+
# all factors should add up to 1.0, scale accordingly
|
|
444
|
+
# print(f"Sum of factors = {np.sum(factors)}")
|
|
445
|
+
# if np.sum(factors) > 1.1:
|
|
446
|
+
# print('Warning: sum of factors is larger than 1.0')
|
|
447
|
+
|
|
448
|
+
directional_scaling = 1.0 / np.sum(factors)
|
|
449
|
+
DirField[iFreq] *= directional_scaling
|
|
450
|
+
|
|
451
|
+
# Calculate m0 of the spectrum
|
|
452
|
+
Sd = [np.sum(DirField[iFreq]) for iFreq in range(len(freq))]
|
|
453
|
+
m0 = np.trapz(Sd, freq)
|
|
454
|
+
Hs_spectrum = 4 * np.sqrt(m0)
|
|
455
|
+
|
|
456
|
+
# scale the spectrum to the given Hs IF Hs_spectrum is not zero
|
|
457
|
+
if Hs_spectrum == 0:
|
|
458
|
+
if Hs > 0:
|
|
459
|
+
warnings.warn(
|
|
460
|
+
f"Spectrum with timestamp {timestamp} has zero significant wave height, can not scale to Hs = {Hs}m"
|
|
461
|
+
)
|
|
462
|
+
else:
|
|
463
|
+
DirField *= (Hs / Hs_spectrum) ** 2
|
|
464
|
+
|
|
465
|
+
# in wavespecrta we work in:
|
|
466
|
+
# m
|
|
467
|
+
# Hz
|
|
468
|
+
# degrees
|
|
469
|
+
# m2/Hz so that is already ok
|
|
470
|
+
dir_step_deg = np.degrees(dir_step)
|
|
471
|
+
efth.append(DirField / dir_step_deg)
|
|
472
|
+
|
|
473
|
+
ts = datetime.datetime.strptime(timestamp, "%m%d%y%H%M%S")
|
|
474
|
+
assert ts.tzinfo is None
|
|
475
|
+
times.append(ts)
|
|
476
|
+
|
|
477
|
+
# from matplotlib import pyplot as plt
|
|
478
|
+
# plt.plot(Hss, label = 'integrated directional spectra')
|
|
479
|
+
|
|
480
|
+
dir_degrees = np.degrees(dDir)
|
|
481
|
+
freq = data[0][1]
|
|
482
|
+
|
|
483
|
+
ds = xr.DataArray(
|
|
484
|
+
data=efth,
|
|
485
|
+
coords={"time": times, "freq": freq, "dir": dir_degrees},
|
|
486
|
+
dims=("time", "freq", "dir"),
|
|
487
|
+
name="efth",
|
|
488
|
+
).to_dataset()
|
|
489
|
+
|
|
490
|
+
# Set attributes
|
|
491
|
+
set_spec_attributes(ds)
|
|
492
|
+
|
|
493
|
+
# add site dimension
|
|
494
|
+
ds["site"] = [0]
|
|
495
|
+
|
|
496
|
+
return ds
|