Passed
Push — master ( 36b2c0...f6dce8 )
by Stefan
03:18
created

sciapy.level2.density.scia_densities.__init__()   A

Complexity

Conditions 1

Size

Total Lines 19
Code Lines 19

Duplication

Lines 19
Ratio 100 %

Code Coverage

Tests 19
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 19
nop 5
dl 19
loc 19
ccs 19
cts 19
cp 1
crap 1
rs 9.45
c 0
b 0
f 0
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):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable dt does not seem to be defined.
Loading history...
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 = [[('gp_id', int),
52
		('alt_max', float), ('alt', float), ('alt_min', float),
53
		('lat_max', float), ('lat', float), ('lat_min', float),
54
		('density', float), ('dens_err_meas', float),
55
		('dens_err_tot', float), ('dens_tot', float)],
56
	[('gp_id', int),
57
		('alt_max', float), ('alt', float), ('alt_min', float),
58
		('lat_max', float), ('lat', float), ('lat_min', float),
59
		('longitude', float),
60
		('density', float), ('dens_err_meas', float),
61
		('dens_err_tot', float), ('dens_tot', float)],
62
	[('gp_id', int),
63
		('alt_max', float), ('alt', float), ('alt_min', float),
64
		('lat_max', float), ('lat', float), ('lat_min', float),
65
		('longitude', float),
66
		('density', float), ('dens_err_meas', float),
67
		('dens_err_tot', float), ('dens_tot', float),
68
		('apriori', float)],
69
	[('gp_id', int),
70
		('alt_max', float), ('alt', float), ('alt_min', float),
71
		('lat_max', float), ('lat', float), ('lat_min', float),
72
		('longitude', float),
73
		('density', float), ('dens_err_meas', float),
74
		('dens_err_tot', float), ('dens_tot', float),
75
		('apriori', float), ('akdiag', float)]]
76
77
78 1
def _unique_values(vals):
79 1
	ldum = []
80 1
	[ldum.append(i) for i in vals if not ldum.count(i)]
81 1
	return np.asarray(ldum).flatten()
82
83
84 1 View Code Duplication
class scia_densities(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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
		if hasattr(filename, 'seek'):
196
			f = filename
197
		else:
198 1
			f = open(filename, 'rb')
199
			# example filename:000NO_orbit_41467_20100203_Dichten.txt
200 1
			fn_fields = os.path.basename(filename).split('_')
201 1
			self.orbit = int(fn_fields[2])
202 1
			self.date = (dt.datetime.strptime(fn_fields[3], "%Y%m%d")
203
						.replace(tzinfo=_UTC) - self.date0).days
204 1
			if self.data_version is None:
205
				# try some heuristics to find the level 2 data version
206 1
				_dir = os.path.dirname(filename)
207 1
				_m = re.search(".*[_-]v([0-9]+[.].*)", _dir)
208 1
				if _m:
209 1
					self.data_version = _m.group(1)
210
				else:
211 1
					self.data_version = "unknown"
212 1
		line = f.readline()
213 1
		data = line.split()
214 1
		mydtype = _meas_dtypes[len(data) - 13]
215 1
		marr = np.genfromtxt(f, dtype=mydtype)
216 1
		f.close()
217
218
		# unique altitudes
219 1
		self.alts_min = _unique_values(marr['alt_min'])
220 1
		self.alts = _unique_values(marr['alt'])
221 1
		self.alts_max = _unique_values(marr['alt_max'])
222
223
		# unique latitudes
224 1
		self.lats_min = _unique_values(marr['lat_min'])
225 1
		self.lats = _unique_values(marr['lat'])
226 1
		self.lats_max = _unique_values(marr['lat_max'])
227
228
		# unique longitudes if available
229 1
		try:
230 1
			self.lons = _unique_values(marr['longitude'])
231
		except:
232
			pass
233
234 1
		self.nalt = len(self.alts)
235 1
		self.nlat = len(self.lats)
236 1
		self.nlon = len(self.lons)
237
238
		# reorder by latitude first, then altitude
239 1
		self.densities = marr['density'].flatten().reshape(self.nalt, self.nlat).transpose()
240 1
		self.dens_err_meas = marr['dens_err_meas'].flatten().reshape(self.nalt, self.nlat).transpose()
241 1
		self.dens_err_tot = marr['dens_err_tot'].flatten().reshape(self.nalt, self.nlat).transpose()
242 1
		self.dens_tot = marr['dens_tot'].flatten().reshape(self.nalt, self.nlat).transpose()
243
244
		# apriori if available
245 1
		try:
246 1
			self.apriori = marr['apriori'].flatten().reshape(self.nalt, self.nlat).transpose()
247
		except:
248
			pass
249
		# akdiag if available
250 1
		try:
251 1
			self.akdiag = marr['akdiag'].flatten().reshape(self.nalt, self.nlat).transpose()
252
		except:
253
			pass
254
255 1
	def write_to_textfile(self, filename):
256
		"""Write NO densities to ascii table files
257
258
		Parameters
259
		----------
260
		filename: str or file object or io.TextIOBase.buffer
261
			The filename or stream to write the data to. For writing to
262
			stdout in python 3, pass `sys.stdout.buffer`.
263
		"""
264 1
		if hasattr(filename, 'seek'):
265
			f = filename
266
		else:
267 1
			f = open(filename, 'w')
268
269 1
		header = "%5s %13s %12s %13s %13s %12s %13s %13s  %13s %12s %12s %12s" % ("GP_ID",
270
				"Max_Hoehe[km]", "Hoehe[km]", "Min_Hoehe[km]",
271
				"Max_Breite[°]", "Breite[°]", "Min_Breite[°]",
272
				"Laenge[°]",
273
				"Dichte[cm^-3]", "Fehler Mess[cm^-3]",
274
				"Fehler tot[cm^-3]", "Gesamtdichte[cm^-3]")
275 1
		if self.apriori is not None:
276 1
			header = header + " %12s" % ("apriori[cm^-3]",)
277 1
		if self.akdiag is not None:
278 1
			header = header + " %12s" % ("AKdiag",)
279 1
		print(header, file=f)
280
281 1
		oformat = "%5i  %+1.5E %+1.5E  %+1.5E  %+1.5E %+1.5E  %+1.5E  %+1.5E   %+1.5E       %+1.5E      %+1.5E        %+1.5E"
282 1
		oformata = "  %+1.5E"
283
284 1
		for i, a in enumerate(self.alts):
285 1
			for j, b in enumerate(self.lats):
286 1
				print(oformat % (i * self.nlat + j,
287
					self.alts_max[i], a, self.alts_min[i],
288
					self.lats_max[j], b, self.lats_min[j], self.lons[j],
289
					self.densities[j, i], self.dens_err_meas[j, i],
290
					self.dens_err_tot[j, i], self.dens_tot[j, i]),
291
					end="", file=f)
292 1
				if self.apriori is not None:
293 1
					print(" " + oformata % self.apriori[j, i], end="", file=f)
294 1
				if self.akdiag is not None:
295 1
					print(" " + oformata % self.akdiag[j, i], end="", file=f)
296 1
				print("", file=f)
297
298 1
	def write_to_netcdf(self, filename, close=True):
299
		"""Write NO densities to netcdf files
300
301
		This function has no stream, i.e. file object, support.
302
303
		Parameters
304
		----------
305
		filename: str
306
			The name of the file to write the data to.
307
		close: bool, optional
308
			Whether or not to close the file after writing.
309
			Setting to `False` enables appending further data
310
			to the same file.
311
			Default: True
312
313
		Returns
314
		-------
315
		Nothing if `close` is True. If `close` is False, returns either an
316
		`netCDF4.Dataset`,
317
		`scipy.io.netcdf.netcdf_file` or
318
		`pupynere.netcdf_file` instance depending on availability.
319
		"""
320 1
		alts_min_out = np.asarray(self.alts_min).reshape(self.nalt)
321 1
		alts_out = np.asarray(self.alts).reshape(self.nalt)
322 1
		alts_max_out = np.asarray(self.alts_max).reshape(self.nalt)
323
324 1
		lats_min_out = np.asarray(self.lats_min).reshape(self.nlat)
325 1
		lats_out = np.asarray(self.lats).reshape(self.nlat)
326 1
		lats_max_out = np.asarray(self.lats_max).reshape(self.nlat)
327
328 1
		ncf = netcdf_file(filename, 'w', **fmtargs)
329
330 1
		if self.version is not None:
331 1
			ncf.version = self.version
332 1
		if self.data_version is not None:
333 1
			ncf.L2_data_version = self.data_version
334
		#ncf.creation_time = dt.datetime.utcnow().replace(tzinfo=_UTC).strftime("%a %b %d %Y %H:%M:%S %z (%Z)")
335 1
		ncf.creation_time = dt.datetime.utcnow().strftime("%a %b %d %Y %H:%M:%S +00:00 (UTC)")
336 1
		ncf.author = self.author
337
338
		# create netcdf file
339 1
		ncf.createDimension('altitude', self.nalt)
340 1
		ncf.createDimension('latitude', self.nlat)
341 1
		ncf.createDimension('time', None)
342
343 1
		forbit = ncf.createVariable('orbit', np.dtype('int64').char, ('time',))
344 1
		ftime = ncf.createVariable('time', np.dtype('int64').char, ('time',))
345
346 1
		falts_min = ncf.createVariable('alt_min', np.dtype('float64').char, ('altitude',))
347 1
		falts = ncf.createVariable('altitude', np.dtype('float64').char, ('altitude',))
348 1
		falts_max = ncf.createVariable('alt_max', np.dtype('float64').char, ('altitude',))
349 1
		flats_min = ncf.createVariable('lat_min', np.dtype('float64').char, ('latitude',))
350 1
		flats = ncf.createVariable('latitude', np.dtype('float64').char, ('latitude',))
351 1
		flats_max = ncf.createVariable('lat_max', np.dtype('float64').char, ('latitude',))
352
353 1
		falts_min.units = 'km'
354 1
		falts_min.positive = 'up'
355 1
		falts.units = 'km'
356 1
		falts.positive = 'up'
357 1
		falts_max.units = 'km'
358 1
		falts_max.positive = 'up'
359 1
		flats_min.units = 'degrees_north'
360 1
		flats.units = 'degrees_north'
361 1
		flats_max.units = 'degrees_north'
362
363 1
		forbit.units = '1'
364 1
		forbit.long_name = 'SCIAMACHY/Envisat orbit number'
365 1
		ftime.units = 'days since {0}'.format(self.date0.isoformat(sep=' '))
366 1
		ftime.standard_name = 'time'
367
368 1
		fdens = ncf.createVariable('density', np.dtype('float64').char, ('time', 'latitude', 'altitude'))
369 1
		fdens.units = 'cm^{-3}'
370 1
		fdens.standard_name = 'number_concentration_of_nitrogen_monoxide_molecules_in_air'
371 1
		fdens_err_meas = ncf.createVariable('error_meas', np.dtype('float64').char, ('time', 'latitude', 'altitude'))
372 1
		fdens_err_meas.units = 'cm^{-3}'
373 1
		fdens_err_meas.long_name = 'NO number density measurement error'
374 1
		fdens_err_tot = ncf.createVariable('error_tot', np.dtype('float64').char, ('time', 'latitude', 'altitude'))
375 1
		fdens_err_tot.units = 'cm^{-3}'
376 1
		fdens_err_tot.long_name = 'NO number density total error'
377 1
		fdens_tot = ncf.createVariable('density_air', np.dtype('float64').char, ('time', 'latitude', 'altitude'))
378 1
		fdens_tot.units = 'cm^{-3}'
379 1
		fdens_tot.long_name = 'approximate overall number concentration of air molecules (NRLMSIS-00)'
380
381 1
		ftime[:] = self.date
382 1
		forbit[:] = self.orbit
383
384 1
		falts_min[:] = alts_min_out
385 1
		falts[:] = alts_out
386 1
		falts_max[:] = alts_max_out
387 1
		flats_min[:] = lats_min_out
388 1
		flats[:] = lats_out
389 1
		flats_max[:] = lats_max_out
390
		# reorder by latitude first, then altitude
391 1
		fdens[0, :] = self.densities
392
		# reorder by latitude first, then altitude
393 1
		fdens_err_meas[0, :] = self.dens_err_meas
394 1
		fdens_err_tot[0, :] = self.dens_err_tot
395 1
		fdens_tot[0, :] = self.dens_tot
396
397
		# longitudes if they are available
398 1
		if self.nlon > 0:
399 1
			lons_out = np.asarray(self.lons).reshape(self.nlon)
400 1
			flons = ncf.createVariable('longitude', np.dtype('float64').char, ('time', 'latitude',))
401 1
			flons.units = 'degrees_east'
402 1
			flons[0, :] = lons_out
403
404 1
		if self.apriori is not None:
405 1
			fapriori = ncf.createVariable('apriori',
406
					np.dtype('float64').char, ('time', 'latitude', 'altitude'))
407 1
			fapriori.units = 'cm^{-3}'
408 1
			fapriori.long_name = 'apriori NO number density'
409 1
			fapriori[0, :] = self.apriori
410
411 1
		if self.akdiag is not None:
412 1
			fakdiag = ncf.createVariable('akm_diagonal',
413
					np.dtype('float64').char, ('time', 'latitude', 'altitude'))
414 1
			fakdiag.units = '1'
415 1
			fakdiag.long_name = 'averaging kernel matrix diagonal element'
416 1
			fakdiag[0, :] = self.akdiag
417 1
		if close:
418 1
			ncf.close()
419
		else:
420 1
			return ncf
421
422 1
	def read_from_netcdf(self, filename, close=True):
423
		"""Read NO densities from netcdf files
424
425
		This function has no stream, i.e. file object support.
426
427
		Parameters
428
		----------
429
		filename: str
430
			The filename to read the data from.
431
		close: bool, optional
432
			Whether or not to close the file after reading.
433
			Setting to `False` enables reading further data
434
			from the same file.
435
			Default: True
436
437
		Returns
438
		-------
439
		Nothing if `close` is True. If `close` is False, returns either an
440
		`netCDF4.Dataset`,
441
		`scipy.io.netcdf.netcdf_file` or
442
		`pupynere.netcdf_file` instance depending on availability.
443
		"""
444 1
		ncf = netcdf_file(filename, 'r')
445
446 1
		try:
447 1
			self.author = ncf.author
448
		except AttributeError:
449
			pass
450 1
		try:
451 1
			self.version = ncf.version
452 1
		except AttributeError:
453 1
			pass
454 1
		try:
455 1
			self.data_version = ncf.L2_data_version
456
		except AttributeError:
457
			pass
458
459 1
		self.nalt = len(ncf.dimensions['altitude'])
460 1
		self.nlat = len(ncf.dimensions['latitude'])
461
462 1
		self.alts_min = ncf.variables['alt_min'][:]
463 1
		self.alts = ncf.variables['altitude'][:]
464 1
		self.alts_max = ncf.variables['alt_max'][:]
465 1
		self.lats_min = ncf.variables['lat_min'][:]
466 1
		self.lats = ncf.variables['latitude'][:]
467 1
		self.lats_max = ncf.variables['lat_max'][:]
468
469 1
		self.date = ncf.variables['time'][:]
470 1
		self.orbit = ncf.variables['orbit'][:]
471
472 1
		self.densities = ncf.variables['density'][:]
473 1
		self.dens_err_meas = ncf.variables['error_meas'][:]
474 1
		self.dens_err_tot = ncf.variables['error_tot'][:]
475 1
		self.dens_tot = ncf.variables['density_air'][:]
476
477
		# longitudes if they are available
478 1
		try:
479 1
			self.lons = ncf.variables['longitude'][:]
480 1
			self.nlon = self.lons.shape[1]
481
		except:
482
			pass
483
484
		# apriori
485 1
		try:
486 1
			self.apriori = ncf.variables['apriori'][:]
487
		except:
488
			pass
489
490
		# akm diagonal elements
491 1
		try:
492 1
			self.akdiag = ncf.variables['akm_diagonal'][:]
493
		except:
494
			pass
495
496 1
		if close:
497 1
			ncf.close()
498
		else:
499
			return ncf
500
501 1
	def read_from_file(self, filename):
502
		"""Wrapper to read NO desnities from files
503
504
		Simple wrapper to delegate reading the data from either netcdf
505
		or ascii files. Poor man's logic: simply try netcdf first, and
506
		if that fails, read as ascii.
507
508
		Parameters
509
		----------
510
		filename: str
511
			The filename to read the data from.
512
		"""
513 1
		try:
514
			# try netcdf first
515 1
			self.read_from_netcdf(filename)
516 1
		except:
517
			# fall back to text file as a last resort
518 1
			self.read_from_textfile(filename)
519
520
521 1 View Code Duplication
def main(*args):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
522
	argc = len(sys.argv)
523
	if argc < 2:
524
		print("Not enough arguments, Usage:\n"
525
			"{0} [input] output [< input]".format(sys.argv[0]))
526
		sys.exit(1)
527
	elif argc < 3:
528
		try:
529
			infile = sys.stdin.buffer  # Python 3
530
		except AttributeError:
531
			infile = sys.stdin
532
		outfile = sys.argv[1]
533
	else:
534
		infile = sys.argv[1]
535
		outfile = sys.argv[2]
536
	sdl = scia_densities()
537
	sdl.read_from_file(infile)
0 ignored issues
show
introduced by
The variable infile does not seem to be defined for all execution paths.
Loading history...
538
	sdl.write_to_netcdf(outfile)
0 ignored issues
show
introduced by
The variable outfile does not seem to be defined for all execution paths.
Loading history...
539
540
541 1
if __name__ == "__main__":
542
	sys.exit(main())
543