|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
# vim:fileencoding=utf-8 |
|
3
|
|
|
# |
|
4
|
|
|
# Copyright (c) 2015-2018 Stefan Bender |
|
5
|
|
|
# |
|
6
|
|
|
# This file is part of sciapy. |
|
7
|
|
|
# sciapy is free software: you can redistribute it or modify it |
|
8
|
|
|
# under the terms of the GNU General Public License as published by |
|
9
|
|
|
# the Free Software Foundation, version 2. |
|
10
|
|
|
# See accompanying LICENSE file or http://www.gnu.org/licenses/gpl-2.0.html. |
|
11
|
1 |
|
"""SCIAMACHY level 2 number density retrieval results interface |
|
12
|
|
|
|
|
13
|
|
|
Interface classes for the level 2 retrieval results from text (ascii) |
|
14
|
|
|
files and netcdf files for further processing. |
|
15
|
|
|
""" |
|
16
|
|
|
|
|
17
|
1 |
|
from __future__ import absolute_import, division, print_function |
|
18
|
|
|
|
|
19
|
1 |
|
import os |
|
20
|
1 |
|
import re |
|
21
|
1 |
|
import sys |
|
22
|
1 |
|
import datetime as dt |
|
23
|
|
|
|
|
24
|
1 |
|
import numpy as np |
|
25
|
1 |
|
try: |
|
26
|
1 |
|
from netCDF4 import Dataset as netcdf_file |
|
27
|
1 |
|
fmtargs = {"format": "NETCDF4"} |
|
28
|
|
|
except ImportError: |
|
29
|
|
|
try: |
|
30
|
|
|
from scipy.io.netcdf import netcdf_file |
|
31
|
|
|
fmtargs = {"version": 1} |
|
32
|
|
|
except ImportError: |
|
33
|
|
|
from pupynere import netcdf_file |
|
34
|
|
|
fmtargs = {"version": 1} |
|
35
|
|
|
|
|
36
|
1 |
|
__all__ = ["scia_densities", "_UTC"] |
|
37
|
|
|
|
|
38
|
1 |
|
try: |
|
39
|
1 |
|
_UTC = dt.timezone.utc |
|
40
|
|
|
except AttributeError: |
|
41
|
|
|
# python 2.7 |
|
42
|
|
|
class UTC(dt.tzinfo): |
|
|
|
|
|
|
43
|
|
|
def utcoffset(self, d): |
|
44
|
|
|
return dt.timedelta(0) |
|
45
|
|
|
def tzname(self, d): |
|
46
|
|
|
return "UTC" |
|
47
|
|
|
def dst(self, d): |
|
48
|
|
|
return dt.timedelta(0) |
|
49
|
|
|
_UTC = UTC() |
|
50
|
|
|
|
|
51
|
1 |
|
_meas_dtypes = [ |
|
52
|
|
|
# initial output << v1.0 |
|
53
|
|
|
[('gp_id', int), |
|
54
|
|
|
('alt_max', float), ('alt', float), ('alt_min', float), |
|
55
|
|
|
('lat_max', float), ('lat', float), ('lat_min', float), |
|
56
|
|
|
('density', float), ('dens_err_meas', float), |
|
57
|
|
|
('dens_err_tot', float), ('dens_tot', float)], |
|
58
|
|
|
# < 1.0 (NO_emiss-178-g729efb0) |
|
59
|
|
|
[('gp_id', int), |
|
60
|
|
|
('alt_max', float), ('alt', float), ('alt_min', float), |
|
61
|
|
|
('lat_max', float), ('lat', float), ('lat_min', float), |
|
62
|
|
|
('longitude', float), |
|
63
|
|
|
('density', float), ('dens_err_meas', float), |
|
64
|
|
|
('dens_err_tot', float), ('dens_tot', float)], |
|
65
|
|
|
# < 1.5 (NO_emiss-183-gcaa9349) |
|
66
|
|
|
[('gp_id', int), |
|
67
|
|
|
('alt_max', float), ('alt', float), ('alt_min', float), |
|
68
|
|
|
('lat_max', float), ('lat', float), ('lat_min', float), |
|
69
|
|
|
('longitude', float), |
|
70
|
|
|
('density', float), ('dens_err_meas', float), |
|
71
|
|
|
('dens_err_tot', float), ('dens_tot', float), |
|
72
|
|
|
('apriori', float)], |
|
73
|
|
|
# >= 1.5 (NO-v1.5) |
|
74
|
|
|
[('gp_id', int), |
|
75
|
|
|
('alt_max', float), ('alt', float), ('alt_min', float), |
|
76
|
|
|
('lat_max', float), ('lat', float), ('lat_min', float), |
|
77
|
|
|
('longitude', float), |
|
78
|
|
|
('density', float), ('dens_err_meas', float), |
|
79
|
|
|
('dens_err_tot', float), ('dens_tot', float), |
|
80
|
|
|
('apriori', float), ('akdiag', float)], |
|
81
|
|
|
] |
|
82
|
|
|
|
|
83
|
|
|
|
|
84
|
1 |
View Code Duplication |
class scia_densities(object): |
|
|
|
|
|
|
85
|
|
|
"""SCIAMACHY orbital retrieved number densities |
|
86
|
|
|
|
|
87
|
|
|
Class interface to orbit-wise SCIAMACHY retrieval results. |
|
88
|
|
|
The attributes are based on the text file layout and are |
|
89
|
|
|
tied to the NO retrieval for now. |
|
90
|
|
|
|
|
91
|
|
|
Parameters |
|
92
|
|
|
---------- |
|
93
|
|
|
ref_date: str, optional |
|
94
|
|
|
The reference date on which to base the date calculations on. |
|
95
|
|
|
Default: "2000-01-01" |
|
96
|
|
|
ver: str, optional |
|
97
|
|
|
Explicit density version, used for exporting the data. |
|
98
|
|
|
Not used if set to `None`. |
|
99
|
|
|
Default: `None` |
|
100
|
|
|
data_ver: str, optional |
|
101
|
|
|
Level 2 data version to use, as "ver" used for exporting. |
|
102
|
|
|
Not used if set to `None`. |
|
103
|
|
|
Default: `None` |
|
104
|
|
|
|
|
105
|
|
|
Attributes |
|
106
|
|
|
---------- |
|
107
|
|
|
version |
|
108
|
|
|
file version string |
|
109
|
|
|
data_version |
|
110
|
|
|
level 2 data version |
|
111
|
|
|
date0 |
|
112
|
|
|
reference date |
|
113
|
|
|
nalt |
|
114
|
|
|
number of altitudes in the orbit |
|
115
|
|
|
nlat |
|
116
|
|
|
number of latitudes in the orbit |
|
117
|
|
|
nlon |
|
118
|
|
|
number of longitudes in the orbit, if longitudes are available |
|
119
|
|
|
orbit |
|
120
|
|
|
SCIAMACHY/Envisat orbit number |
|
121
|
|
|
date |
|
122
|
|
|
number of days of the orbit counting from the reference date |
|
123
|
|
|
date0 |
|
124
|
|
|
alts_min |
|
125
|
|
|
alts |
|
126
|
|
|
alts_max |
|
127
|
|
|
the altitude bins: minimum, central, and maximum altitude |
|
128
|
|
|
lats_min |
|
129
|
|
|
lats |
|
130
|
|
|
lats_max |
|
131
|
|
|
the latitude bins: minimum, central, and maximum latitude |
|
132
|
|
|
lons: |
|
133
|
|
|
the central longitude of the bins, only used if available |
|
134
|
|
|
|
|
135
|
|
|
densities |
|
136
|
|
|
NO number densities in the bins, (nlat, nalt) array_like |
|
137
|
|
|
dens_err_meas |
|
138
|
|
|
NO number densities measurement uncertainty, |
|
139
|
|
|
(nlat, nalt) array_like |
|
140
|
|
|
dens_err_tot |
|
141
|
|
|
NO number densities total uncertainty, (nlat, nalt) array_like |
|
142
|
|
|
dens_tot |
|
143
|
|
|
total number densities calculated and interpolated NRLMSIS-00 |
|
144
|
|
|
values, (nlat, nalt) array_like |
|
145
|
|
|
|
|
146
|
|
|
apriori |
|
147
|
|
|
prior NO number densities, (nlat, nalt) array_like if available, |
|
148
|
|
|
otherwise `None` |
|
149
|
|
|
akdiag |
|
150
|
|
|
diagonal element of the averaging kernel matrix at the retrieval |
|
151
|
|
|
grid point. (nlat, nalt) array_like if available otherwise `None` |
|
152
|
|
|
|
|
153
|
|
|
Methods |
|
154
|
|
|
------- |
|
155
|
|
|
read_from_textfile |
|
156
|
|
|
read_from_netcdf |
|
157
|
|
|
read_from_file |
|
158
|
|
|
write_to_textfile |
|
159
|
|
|
write_to_netcdf |
|
160
|
|
|
|
|
161
|
|
|
Note |
|
162
|
|
|
---- |
|
163
|
|
|
The variables are empty when initialized, use one of the |
|
164
|
|
|
read_from_...() methods to fill with actual data. |
|
165
|
|
|
""" |
|
166
|
1 |
|
def __init__(self, author="unknown", ref_date="2000-01-01", ver=None, data_ver=None): |
|
167
|
1 |
|
self.author = author |
|
168
|
1 |
|
self.version = ver |
|
169
|
1 |
|
self.data_version = data_ver |
|
170
|
1 |
|
self.date0 = dt.datetime.strptime(ref_date, "%Y-%m-%d").replace(tzinfo=_UTC) |
|
171
|
1 |
|
self.nalt = 0 |
|
172
|
1 |
|
self.nlat = 0 |
|
173
|
1 |
|
self.nlon = 0 |
|
174
|
1 |
|
self.orbit = -1 |
|
175
|
1 |
|
self.date = -1 |
|
176
|
1 |
|
self.alts_min = np.array([]) |
|
177
|
1 |
|
self.alts = np.array([]) |
|
178
|
1 |
|
self.alts_max = np.array([]) |
|
179
|
1 |
|
self.lats_min = np.array([]) |
|
180
|
1 |
|
self.lats = np.array([]) |
|
181
|
1 |
|
self.lats_max = np.array([]) |
|
182
|
1 |
|
self.lons = np.array([]) |
|
183
|
1 |
|
self.akdiag = None |
|
184
|
1 |
|
self.apriori = None |
|
185
|
|
|
|
|
186
|
1 |
|
def read_from_textfile(self, filename): |
|
187
|
|
|
"""Read NO densities from ascii table file |
|
188
|
|
|
|
|
189
|
|
|
Parameters |
|
190
|
|
|
---------- |
|
191
|
|
|
filename: str, file object or io.TextIOBase.buffer |
|
192
|
|
|
The filename or stream to read the data from. For example |
|
193
|
|
|
to read from stdin in python 3, pass `sys.stdin.buffer`. |
|
194
|
|
|
""" |
|
195
|
1 |
|
def _unsrt_unique(a): |
|
196
|
1 |
|
return a[np.sort(np.unique(a, return_index=True)[1])] |
|
197
|
|
|
|
|
198
|
1 |
|
if hasattr(filename, 'seek'): |
|
199
|
|
|
f = filename |
|
200
|
|
|
else: |
|
201
|
1 |
|
f = open(filename, 'rb') |
|
202
|
|
|
# example filename:000NO_orbit_41467_20100203_Dichten.txt |
|
203
|
1 |
|
fn_fields = os.path.basename(filename).split('_') |
|
204
|
1 |
|
self.orbit = int(fn_fields[2]) |
|
205
|
1 |
|
self.date = (dt.datetime.strptime(fn_fields[3], "%Y%m%d") |
|
206
|
|
|
.replace(tzinfo=_UTC) - self.date0).days |
|
207
|
1 |
|
if self.data_version is None: |
|
208
|
|
|
# try some heuristics to find the level 2 data version |
|
209
|
1 |
|
_dir = os.path.dirname(filename) |
|
210
|
1 |
|
_m = re.search(".*[_-]v([0-9]+[.].*)", _dir) |
|
211
|
1 |
|
if _m: |
|
212
|
1 |
|
self.data_version = _m.group(1) |
|
213
|
|
|
else: |
|
214
|
1 |
|
self.data_version = "unknown" |
|
215
|
1 |
|
data = f.readline().split() |
|
216
|
1 |
|
mydtype = _meas_dtypes[len(data) - 13] |
|
217
|
1 |
|
marr = np.genfromtxt(f, dtype=mydtype) |
|
218
|
1 |
|
f.close() |
|
219
|
|
|
|
|
220
|
|
|
# unique altitudes |
|
221
|
1 |
|
self.alts_min = _unsrt_unique(marr['alt_min']) |
|
222
|
1 |
|
self.alts = _unsrt_unique(marr['alt']) |
|
223
|
1 |
|
self.alts_max = _unsrt_unique(marr['alt_max']) |
|
224
|
|
|
|
|
225
|
|
|
# unique latitudes |
|
226
|
1 |
|
self.lats_min = _unsrt_unique(marr['lat_min']) |
|
227
|
1 |
|
self.lats = _unsrt_unique(marr['lat']) |
|
228
|
1 |
|
self.lats_max = _unsrt_unique(marr['lat_max']) |
|
229
|
|
|
|
|
230
|
|
|
# unique longitudes if available |
|
231
|
1 |
|
try: |
|
232
|
1 |
|
self.lons = _unsrt_unique(marr['longitude']) |
|
233
|
1 |
|
self.nlon = len(self.lons) |
|
234
|
1 |
|
except ValueError: |
|
235
|
1 |
|
pass |
|
236
|
|
|
|
|
237
|
1 |
|
self.nalt = len(self.alts) |
|
238
|
1 |
|
self.nlat = len(self.lats) |
|
239
|
|
|
|
|
240
|
|
|
# reorder by latitude first, then altitude |
|
241
|
1 |
|
self.densities = marr['density'].flatten().reshape(self.nalt, self.nlat).transpose() |
|
242
|
1 |
|
self.dens_err_meas = marr['dens_err_meas'].flatten().reshape(self.nalt, self.nlat).transpose() |
|
243
|
1 |
|
self.dens_err_tot = marr['dens_err_tot'].flatten().reshape(self.nalt, self.nlat).transpose() |
|
244
|
1 |
|
self.dens_tot = marr['dens_tot'].flatten().reshape(self.nalt, self.nlat).transpose() |
|
245
|
|
|
|
|
246
|
|
|
# apriori if available |
|
247
|
1 |
|
try: |
|
248
|
1 |
|
self.apriori = marr['apriori'].flatten().reshape(self.nalt, self.nlat).transpose() |
|
249
|
1 |
|
except ValueError: |
|
250
|
1 |
|
pass |
|
251
|
|
|
# akdiag if available |
|
252
|
1 |
|
try: |
|
253
|
1 |
|
self.akdiag = marr['akdiag'].flatten().reshape(self.nalt, self.nlat).transpose() |
|
254
|
1 |
|
except ValueError: |
|
255
|
1 |
|
pass |
|
256
|
|
|
|
|
257
|
1 |
|
def write_to_textfile(self, filename): |
|
258
|
|
|
"""Write NO densities to ascii table files |
|
259
|
|
|
|
|
260
|
|
|
Parameters |
|
261
|
|
|
---------- |
|
262
|
|
|
filename: str or file object or io.TextIOBase.buffer |
|
263
|
|
|
The filename or stream to write the data to. For writing to |
|
264
|
|
|
stdout in python 3, pass `sys.stdout.buffer`. |
|
265
|
|
|
""" |
|
266
|
1 |
|
if hasattr(filename, 'seek'): |
|
267
|
|
|
f = filename |
|
268
|
|
|
else: |
|
269
|
1 |
|
f = open(filename, 'w') |
|
270
|
|
|
|
|
271
|
1 |
|
header = "%5s %13s %12s %13s %13s %12s %13s %13s %12s %12s %12s" % ("GP_ID", |
|
272
|
|
|
"Max_Hoehe[km]", "Hoehe[km]", "Min_Hoehe[km]", |
|
273
|
|
|
"Max_Breite[°]", "Breite[°]", "Min_Breite[°]", |
|
274
|
|
|
"Dichte[cm^-3]", "Fehler Mess[cm^-3]", |
|
275
|
|
|
"Fehler tot[cm^-3]", "Gesamtdichte[cm^-3]") |
|
276
|
1 |
|
if self.nlon > 0: |
|
277
|
1 |
|
header = header[:87] + " %13s" % ("Laenge[°]",) + header[87:] |
|
278
|
1 |
|
if self.apriori is not None: |
|
279
|
1 |
|
header = header + " %12s" % ("apriori[cm^-3]",) |
|
280
|
1 |
|
if self.akdiag is not None: |
|
281
|
1 |
|
header = header + " %12s" % ("AKdiag",) |
|
282
|
1 |
|
print(header, file=f) |
|
283
|
|
|
|
|
284
|
1 |
|
oformat = "%5i %+1.5E %+1.5E %+1.5E %+1.5E %+1.5E %+1.5E %+1.5E %+1.5E %+1.5E %+1.5E" |
|
285
|
1 |
|
if self.nlon > 0: |
|
286
|
1 |
|
oformat = oformat[:49] + " %+1.5E" + oformat[49:] |
|
287
|
1 |
|
oformata = " %+1.5E" |
|
288
|
|
|
|
|
289
|
1 |
|
for i, a in enumerate(self.alts): |
|
290
|
1 |
|
for j, b in enumerate(self.lats): |
|
291
|
1 |
|
line_list = [i * self.nlat + j, |
|
292
|
|
|
self.alts_max[i], a, self.alts_min[i], |
|
293
|
|
|
self.lats_max[j], b, self.lats_min[j], |
|
294
|
|
|
self.densities[j, i], self.dens_err_meas[j, i], |
|
295
|
|
|
self.dens_err_tot[j, i], self.dens_tot[j, i]] |
|
296
|
1 |
|
if self.nlon > 0: |
|
297
|
1 |
|
line_list.insert(7, self.lons[j]) |
|
298
|
1 |
|
print(oformat % tuple(line_list), |
|
299
|
|
|
end="", file=f) |
|
300
|
1 |
|
if self.apriori is not None: |
|
301
|
1 |
|
print(" " + oformata % self.apriori[j, i], end="", file=f) |
|
302
|
1 |
|
if self.akdiag is not None: |
|
303
|
1 |
|
print(" " + oformata % self.akdiag[j, i], end="", file=f) |
|
304
|
1 |
|
print("", file=f) |
|
305
|
|
|
|
|
306
|
1 |
|
def write_to_netcdf(self, filename, close=True): |
|
307
|
|
|
"""Write NO densities to netcdf files |
|
308
|
|
|
|
|
309
|
|
|
This function has no stream, i.e. file object, support. |
|
310
|
|
|
|
|
311
|
|
|
Parameters |
|
312
|
|
|
---------- |
|
313
|
|
|
filename: str |
|
314
|
|
|
The name of the file to write the data to. |
|
315
|
|
|
close: bool, optional |
|
316
|
|
|
Whether or not to close the file after writing. |
|
317
|
|
|
Setting to `False` enables appending further data |
|
318
|
|
|
to the same file. |
|
319
|
|
|
Default: True |
|
320
|
|
|
|
|
321
|
|
|
Returns |
|
322
|
|
|
------- |
|
323
|
|
|
Nothing if `close` is True. If `close` is False, returns either an |
|
324
|
|
|
`netCDF4.Dataset`, |
|
325
|
|
|
`scipy.io.netcdf.netcdf_file` or |
|
326
|
|
|
`pupynere.netcdf_file` instance depending on availability. |
|
327
|
|
|
""" |
|
328
|
1 |
|
alts_min_out = np.asarray(self.alts_min).reshape(self.nalt) |
|
329
|
1 |
|
alts_out = np.asarray(self.alts).reshape(self.nalt) |
|
330
|
1 |
|
alts_max_out = np.asarray(self.alts_max).reshape(self.nalt) |
|
331
|
|
|
|
|
332
|
1 |
|
lats_min_out = np.asarray(self.lats_min).reshape(self.nlat) |
|
333
|
1 |
|
lats_out = np.asarray(self.lats).reshape(self.nlat) |
|
334
|
1 |
|
lats_max_out = np.asarray(self.lats_max).reshape(self.nlat) |
|
335
|
|
|
|
|
336
|
1 |
|
ncf = netcdf_file(filename, 'w', **fmtargs) |
|
337
|
|
|
|
|
338
|
1 |
|
if self.version is not None: |
|
339
|
1 |
|
ncf.version = self.version |
|
340
|
1 |
|
if self.data_version is not None: |
|
341
|
1 |
|
ncf.L2_data_version = self.data_version |
|
342
|
|
|
#ncf.creation_time = dt.datetime.utcnow().replace(tzinfo=_UTC).strftime("%a %b %d %Y %H:%M:%S %z (%Z)") |
|
343
|
1 |
|
ncf.creation_time = dt.datetime.utcnow().strftime("%a %b %d %Y %H:%M:%S +00:00 (UTC)") |
|
344
|
1 |
|
ncf.author = self.author |
|
345
|
|
|
|
|
346
|
|
|
# create netcdf file |
|
347
|
1 |
|
ncf.createDimension('time', None) |
|
348
|
1 |
|
ncf.createDimension('altitude', self.nalt) |
|
349
|
1 |
|
ncf.createDimension('latitude', self.nlat) |
|
350
|
|
|
|
|
351
|
1 |
|
forbit = ncf.createVariable('orbit', np.dtype('int32').char, ('time',)) |
|
352
|
1 |
|
ftime = ncf.createVariable('time', np.dtype('int32').char, ('time',)) |
|
353
|
|
|
|
|
354
|
1 |
|
falts_min = ncf.createVariable('alt_min', np.dtype('float64').char, ('altitude',)) |
|
355
|
1 |
|
falts = ncf.createVariable('altitude', np.dtype('float64').char, ('altitude',)) |
|
356
|
1 |
|
falts_max = ncf.createVariable('alt_max', np.dtype('float64').char, ('altitude',)) |
|
357
|
1 |
|
flats_min = ncf.createVariable('lat_min', np.dtype('float64').char, ('latitude',)) |
|
358
|
1 |
|
flats = ncf.createVariable('latitude', np.dtype('float64').char, ('latitude',)) |
|
359
|
1 |
|
flats_max = ncf.createVariable('lat_max', np.dtype('float64').char, ('latitude',)) |
|
360
|
|
|
|
|
361
|
1 |
|
falts_min.units = 'km' |
|
362
|
1 |
|
falts_min.positive = 'up' |
|
363
|
1 |
|
falts.units = 'km' |
|
364
|
1 |
|
falts.positive = 'up' |
|
365
|
1 |
|
falts_max.units = 'km' |
|
366
|
1 |
|
falts_max.positive = 'up' |
|
367
|
1 |
|
flats_min.units = 'degrees_north' |
|
368
|
1 |
|
flats.units = 'degrees_north' |
|
369
|
1 |
|
flats_max.units = 'degrees_north' |
|
370
|
|
|
|
|
371
|
1 |
|
forbit.units = '1' |
|
372
|
1 |
|
forbit.long_name = 'SCIAMACHY/Envisat orbit number' |
|
373
|
1 |
|
ftime.units = 'days since {0}'.format(self.date0.isoformat(sep=' ')) |
|
374
|
1 |
|
ftime.standard_name = 'time' |
|
375
|
|
|
|
|
376
|
1 |
|
fdens = ncf.createVariable('density', np.dtype('float64').char, ('time', 'latitude', 'altitude')) |
|
377
|
1 |
|
fdens.units = 'cm^{-3}' |
|
378
|
1 |
|
fdens.standard_name = 'number_concentration_of_nitrogen_monoxide_molecules_in_air' |
|
379
|
1 |
|
fdens_err_meas = ncf.createVariable('error_meas', np.dtype('float64').char, ('time', 'latitude', 'altitude')) |
|
380
|
1 |
|
fdens_err_meas.units = 'cm^{-3}' |
|
381
|
1 |
|
fdens_err_meas.long_name = 'NO number density measurement error' |
|
382
|
1 |
|
fdens_err_tot = ncf.createVariable('error_tot', np.dtype('float64').char, ('time', 'latitude', 'altitude')) |
|
383
|
1 |
|
fdens_err_tot.units = 'cm^{-3}' |
|
384
|
1 |
|
fdens_err_tot.long_name = 'NO number density total error' |
|
385
|
1 |
|
fdens_tot = ncf.createVariable('density_air', np.dtype('float64').char, ('time', 'latitude', 'altitude')) |
|
386
|
1 |
|
fdens_tot.units = 'cm^{-3}' |
|
387
|
1 |
|
fdens_tot.long_name = 'approximate overall number concentration of air molecules (NRLMSIS-00)' |
|
388
|
|
|
|
|
389
|
1 |
|
ftime[:] = [self.date] |
|
390
|
1 |
|
forbit[:] = [self.orbit] |
|
391
|
|
|
|
|
392
|
1 |
|
falts_min[:] = alts_min_out |
|
393
|
1 |
|
falts[:] = alts_out |
|
394
|
1 |
|
falts_max[:] = alts_max_out |
|
395
|
1 |
|
flats_min[:] = lats_min_out |
|
396
|
1 |
|
flats[:] = lats_out |
|
397
|
1 |
|
flats_max[:] = lats_max_out |
|
398
|
|
|
# reorder by latitude first, then altitude |
|
399
|
1 |
|
fdens[0, :] = self.densities |
|
400
|
|
|
# reorder by latitude first, then altitude |
|
401
|
1 |
|
fdens_err_meas[0, :] = self.dens_err_meas |
|
402
|
1 |
|
fdens_err_tot[0, :] = self.dens_err_tot |
|
403
|
1 |
|
fdens_tot[0, :] = self.dens_tot |
|
404
|
|
|
|
|
405
|
|
|
# longitudes if they are available |
|
406
|
1 |
|
if self.nlon > 0: |
|
407
|
1 |
|
lons_out = np.asarray(self.lons).reshape(self.nlon) |
|
408
|
1 |
|
flons = ncf.createVariable('longitude', np.dtype('float64').char, ('time', 'latitude',)) |
|
409
|
1 |
|
flons.units = 'degrees_east' |
|
410
|
1 |
|
flons[0, :] = lons_out |
|
411
|
|
|
|
|
412
|
1 |
|
if self.apriori is not None: |
|
413
|
1 |
|
fapriori = ncf.createVariable('apriori', |
|
414
|
|
|
np.dtype('float64').char, ('time', 'latitude', 'altitude')) |
|
415
|
1 |
|
fapriori.units = 'cm^{-3}' |
|
416
|
1 |
|
fapriori.long_name = 'apriori NO number density' |
|
417
|
1 |
|
fapriori[0, :] = self.apriori |
|
418
|
|
|
|
|
419
|
1 |
|
if self.akdiag is not None: |
|
420
|
1 |
|
fakdiag = ncf.createVariable('akm_diagonal', |
|
421
|
|
|
np.dtype('float64').char, ('time', 'latitude', 'altitude')) |
|
422
|
1 |
|
fakdiag.units = '1' |
|
423
|
1 |
|
fakdiag.long_name = 'averaging kernel matrix diagonal element' |
|
424
|
1 |
|
fakdiag[0, :] = self.akdiag |
|
425
|
1 |
|
if close: |
|
426
|
1 |
|
ncf.close() |
|
427
|
|
|
else: |
|
428
|
1 |
|
return ncf |
|
429
|
|
|
|
|
430
|
1 |
|
def read_from_netcdf(self, filename, close=True): |
|
431
|
|
|
"""Read NO densities from netcdf files |
|
432
|
|
|
|
|
433
|
|
|
This function has no stream, i.e. file object support. |
|
434
|
|
|
|
|
435
|
|
|
Parameters |
|
436
|
|
|
---------- |
|
437
|
|
|
filename: str |
|
438
|
|
|
The filename to read the data from. |
|
439
|
|
|
close: bool, optional |
|
440
|
|
|
Whether or not to close the file after reading. |
|
441
|
|
|
Setting to `False` enables reading further data |
|
442
|
|
|
from the same file. |
|
443
|
|
|
Default: True |
|
444
|
|
|
|
|
445
|
|
|
Returns |
|
446
|
|
|
------- |
|
447
|
|
|
Nothing if `close` is True. If `close` is False, returns either an |
|
448
|
|
|
`netCDF4.Dataset`, |
|
449
|
|
|
`scipy.io.netcdf.netcdf_file` or |
|
450
|
|
|
`pupynere.netcdf_file` instance depending on availability. |
|
451
|
|
|
""" |
|
452
|
1 |
|
def _try_decode(s): |
|
453
|
1 |
|
if hasattr(s, "decode"): |
|
454
|
|
|
return s.decode() |
|
455
|
1 |
|
return s |
|
456
|
1 |
|
ncf = netcdf_file(filename, 'r') |
|
457
|
|
|
|
|
458
|
1 |
|
try: |
|
459
|
1 |
|
self.author = _try_decode(ncf.author) |
|
460
|
|
|
except AttributeError: |
|
461
|
|
|
pass |
|
462
|
1 |
|
try: |
|
463
|
1 |
|
self.version = _try_decode(ncf.version) |
|
464
|
1 |
|
except AttributeError: |
|
465
|
1 |
|
pass |
|
466
|
1 |
|
try: |
|
467
|
1 |
|
self.data_version = _try_decode(ncf.L2_data_version) |
|
468
|
|
|
except AttributeError: |
|
469
|
|
|
pass |
|
470
|
|
|
|
|
471
|
1 |
|
self.alts_min = ncf.variables['alt_min'][:].copy() |
|
472
|
1 |
|
self.alts = ncf.variables['altitude'][:].copy() |
|
473
|
1 |
|
self.alts_max = ncf.variables['alt_max'][:].copy() |
|
474
|
1 |
|
self.lats_min = ncf.variables['lat_min'][:].copy() |
|
475
|
1 |
|
self.lats = ncf.variables['latitude'][:].copy() |
|
476
|
1 |
|
self.lats_max = ncf.variables['lat_max'][:].copy() |
|
477
|
|
|
|
|
478
|
1 |
|
self.nalt = len(self.alts) |
|
479
|
1 |
|
self.nlat = len(self.lats) |
|
480
|
|
|
|
|
481
|
1 |
|
self.date = ncf.variables['time'][:].copy() |
|
482
|
1 |
|
self.orbit = ncf.variables['orbit'][:].copy() |
|
483
|
|
|
|
|
484
|
1 |
|
self.densities = ncf.variables['density'][:].copy() |
|
485
|
1 |
|
self.dens_err_meas = ncf.variables['error_meas'][:].copy() |
|
486
|
1 |
|
self.dens_err_tot = ncf.variables['error_tot'][:].copy() |
|
487
|
1 |
|
self.dens_tot = ncf.variables['density_air'][:].copy() |
|
488
|
|
|
|
|
489
|
|
|
# longitudes if they are available |
|
490
|
1 |
|
try: |
|
491
|
1 |
|
self.lons = ncf.variables['longitude'][:].copy() |
|
492
|
1 |
|
self.nlon = self.lons.shape[1] |
|
493
|
1 |
|
except KeyError: |
|
494
|
1 |
|
pass |
|
495
|
|
|
|
|
496
|
|
|
# apriori |
|
497
|
1 |
|
try: |
|
498
|
1 |
|
self.apriori = ncf.variables['apriori'][:].copy() |
|
499
|
1 |
|
except KeyError: |
|
500
|
1 |
|
pass |
|
501
|
|
|
|
|
502
|
|
|
# akm diagonal elements |
|
503
|
1 |
|
try: |
|
504
|
1 |
|
self.akdiag = ncf.variables['akm_diagonal'][:].copy() |
|
505
|
1 |
|
except KeyError: |
|
506
|
1 |
|
pass |
|
507
|
|
|
|
|
508
|
1 |
|
if close: |
|
509
|
1 |
|
ncf.close() |
|
510
|
|
|
else: |
|
511
|
|
|
return ncf |
|
512
|
|
|
|
|
513
|
1 |
|
def read_from_file(self, filename): |
|
514
|
|
|
"""Wrapper to read NO desnities from files |
|
515
|
|
|
|
|
516
|
|
|
Simple wrapper to delegate reading the data from either netcdf |
|
517
|
|
|
or ascii files. Poor man's logic: simply try netcdf first, and |
|
518
|
|
|
if that fails, read as ascii. |
|
519
|
|
|
|
|
520
|
|
|
Parameters |
|
521
|
|
|
---------- |
|
522
|
|
|
filename: str |
|
523
|
|
|
The filename to read the data from. |
|
524
|
|
|
""" |
|
525
|
1 |
|
try: |
|
526
|
|
|
# try netcdf first |
|
527
|
1 |
|
self.read_from_netcdf(filename) |
|
528
|
1 |
|
except (IOError, OSError, TypeError): |
|
529
|
|
|
# fall back to text file as a last resort |
|
530
|
1 |
|
self.read_from_textfile(filename) |
|
531
|
|
|
|
|
532
|
|
|
|
|
533
|
1 |
View Code Duplication |
def main(*args): |
|
|
|
|
|
|
534
|
|
|
argc = len(sys.argv) |
|
535
|
|
|
if argc < 2: |
|
536
|
|
|
print("Not enough arguments, Usage:\n" |
|
537
|
|
|
"{0} [input] output [< input]".format(sys.argv[0])) |
|
538
|
|
|
sys.exit(1) |
|
539
|
|
|
elif argc < 3: |
|
540
|
|
|
try: |
|
541
|
|
|
infile = sys.stdin.buffer # Python 3 |
|
542
|
|
|
except AttributeError: |
|
543
|
|
|
infile = sys.stdin |
|
544
|
|
|
outfile = sys.argv[1] |
|
545
|
|
|
else: |
|
546
|
|
|
infile = sys.argv[1] |
|
547
|
|
|
outfile = sys.argv[2] |
|
548
|
|
|
sdl = scia_densities() |
|
549
|
|
|
sdl.read_from_file(infile) |
|
|
|
|
|
|
550
|
|
|
sdl.write_to_netcdf(outfile) |
|
|
|
|
|
|
551
|
|
|
|
|
552
|
|
|
|
|
553
|
1 |
|
if __name__ == "__main__": |
|
554
|
|
|
sys.exit(main()) |
|
555
|
|
|
|