Passed
Push — master ( bdae2e...f0f30c )
by Stefan
03:19
created

sciapy.level2.density.UTC.dst()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 2
ccs 0
cts 2
cp 0
crap 2
rs 10
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 sys
21 1
import datetime as dt
22
23 1
import numpy as np
24 1
try:
25 1
	from netCDF4 import Dataset as netcdf_file
26 1
	fmtargs = {"format": "NETCDF4"}
27
except ImportError:
28
	try:
29
		from scipy.io.netcdf import netcdf_file
30
		fmtargs = {"version": 1}
31
	except ImportError:
32
		from pupynere import netcdf_file
33
		fmtargs = {"version": 1}
34
35 1
__all__ = ["scia_density", "scia_densities"]
36
37 1
try:
38 1
	_UTC = dt.timezone.utc
39
except AttributeError:
40
	# python 2.7
41
	class UTC(dt.tzinfo):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable dt does not seem to be defined.
Loading history...
42
		def utcoffset(self, d):
43
			return dt.timedelta(0)
44
		def tzname(self, d):
45
			return "UTC"
46
		def dst(self, d):
47
			return dt.timedelta(0)
48
	_UTC = UTC()
49
50 1
_meas_dtypes = [[('gp_id', int),
51
		('alt_max', float), ('alt', float), ('alt_min', float),
52
		('lat_max', float), ('lat', float), ('lat_min', float),
53
		('density', float), ('dens_err_meas', float),
54
		('dens_err_tot', float), ('dens_tot', float)],
55
	[('gp_id', int),
56
		('alt_max', float), ('alt', float), ('alt_min', float),
57
		('lat_max', float), ('lat', float), ('lat_min', float),
58
		('longitude', float),
59
		('density', float), ('dens_err_meas', float),
60
		('dens_err_tot', float), ('dens_tot', float)],
61
	[('gp_id', int),
62
		('alt_max', float), ('alt', float), ('alt_min', float),
63
		('lat_max', float), ('lat', float), ('lat_min', float),
64
		('longitude', float),
65
		('density', float), ('dens_err_meas', float),
66
		('dens_err_tot', float), ('dens_tot', float),
67
		('apriori', float)],
68
	[('gp_id', int),
69
		('alt_max', float), ('alt', float), ('alt_min', float),
70
		('lat_max', float), ('lat', float), ('lat_min', float),
71
		('longitude', float),
72
		('density', float), ('dens_err_meas', float),
73
		('dens_err_tot', float), ('dens_tot', float),
74
		('apriori', float), ('akdiag', float)]]
75
76
77 1
def _unique_values(vals):
78 1
	ldum = []
79 1
	[ldum.append(i) for i in vals if not ldum.count(i)]
80 1
	return np.asarray(ldum).flatten()
81
82
83 1 View Code Duplication
class scia_density(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
84
	"""SCIAMACHY single scan retrieved number densities"""
85 1
	def __init__(self):
86
		self.gp_id = 0
87
		self.alt_min = 0.
88
		self.alt = 0.
89
		self.alt_max = 0.
90
		self.lat_min = 0.
91
		self.lat = 0.
92
		self.lat_max = 0.
93
		self.lon = 0.
94
		self.density = 0.
95
		self.dens_err_meas = 0.
96
		self.dens_err_tot = 0.
97
		self.dens_tot = 0.
98
		self.akdiag = 0.
99
		self.apriori = 0.
100
101
102 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...
103
	"""SCIAMACHY orbital retrieved number densities
104
105
	Class interface to orbit-wise SCIAMACHY retrieval results.
106
	The attributes are based on the text file layout and are
107
	tied to the NO retrieval for now.
108
109
	Parameters
110
	----------
111
	ref_date: str, optional
112
		The reference date on which to base the date calculations on.
113
		Default: "2000-01-01"
114
	ver: str, optional
115
		Explicit density version, used for exporting the data.
116
		Not used if set to `None`.
117
		Default: `None`
118
	data_ver: str, optional
119
		Level 2 data version to use, as "ver" used for exporting.
120
		Not used if set to `None`.
121
		Default: `None`
122
123
	Attributes
124
	----------
125
	version
126
		file version string
127
	data_version
128
		level 2 data version
129
	date0
130
		reference date
131
	nalt
132
		number of altitudes in the orbit
133
	nlat
134
		number of latitudes in the orbit
135
	nlon
136
		number of longitudes in the orbit, if longitudes are available
137
	orbit
138
		SCIAMACHY/Envisat orbit number
139
	date
140
		number of days of the orbit counting from the reference date
141
		date0
142
	alts_min
143
	alts
144
	alts_max
145
		the altitude bins: minimum, central, and maximum altitude
146
	lats_min
147
	lats
148
	lats_max
149
		the latitude bins: minimum, central, and maximum latitude
150
	lons:
151
		the central longitude of the bins, only used if available
152
153
	densities
154
		NO number densities in the bins, (nlat, nalt) array_like
155
	dens_err_meas
156
		NO number densities measurement uncertainty,
157
		(nlat, nalt) array_like
158
	dens_err_tot
159
		NO number densities total uncertainty, (nlat, nalt) array_like
160
	dens_tot
161
		total number densities calculated and interpolated NRLMSIS-00
162
		values, (nlat, nalt) array_like
163
164
	apriori
165
		prior NO number densities, (nlat, nalt) array_like if available,
166
		otherwise `None`
167
	akdiag
168
		diagonal element of the averaging kernel matrix at the retrieval
169
		grid point. (nlat, nalt) array_like if available otherwise `None`
170
171
	Methods
172
	-------
173
	read_from_textfile
174
	read_from_netcdf
175
	read_from_file
176
	write_to_textfile
177
	write_to_netcdf
178
179
	Note
180
	----
181
	The variables are empty when initialized, use one of the
182
	read_from_...() methods to fill with actual data.
183
	"""
184 1
	def __init__(self, ref_date="2000-01-01", ver=None, data_ver=None):
185 1
		self.version = ver
186 1
		self.data_version = data_ver
187 1
		self.date0 = dt.datetime.strptime(ref_date, "%Y-%m-%d").replace(tzinfo=_UTC)
188 1
		self.nalt = 0
189 1
		self.nlat = 0
190 1
		self.nlon = 0
191 1
		self.orbit = -1
192 1
		self.date = -1
193 1
		self.alts_min = np.array([])
194 1
		self.alts = np.array([])
195 1
		self.alts_max = np.array([])
196 1
		self.lats_min = np.array([])
197 1
		self.lats = np.array([])
198 1
		self.lats_max = np.array([])
199 1
		self.lons = np.array([])
200 1
		self.akdiag = None
201 1
		self.apriori = None
202
203 1
	def read_from_textfile(self, filename):
204
		"""Read NO densities from ascii table file
205
206
		Parameters
207
		----------
208
		filename: str, file object or io.TextIOBase.buffer
209
			The filename or stream to read the data from. For example
210
			to read from stdin in python 3, pass `sys.stdin.buffer`.
211
		"""
212 1
		if hasattr(filename, 'seek'):
213
			f = filename
214
		else:
215 1
			f = open(filename, 'rb')
216
			# example filename:000NO_orbit_41467_20100203_Dichten.txt
217 1
			fn_fields = os.path.basename(filename).split('_')
218 1
			self.orbit = int(fn_fields[2])
219 1
			self.date = (dt.datetime.strptime(fn_fields[3], "%Y%m%d")
220
						.replace(tzinfo=_UTC) - self.date0).days
221 1
			if self.data_version is None:
222
				# try some heuristics to find the level 2 data version
223 1
				self.data_version = os.path.dirname(filename).split('v')[-1]
224 1
		line = f.readline()
225 1
		data = line.split()
226 1
		mydtype = _meas_dtypes[len(data) - 13]
227 1
		marr = np.genfromtxt(f, dtype=mydtype)
228 1
		f.close()
229
230
		# unique altitudes
231 1
		self.alts_min = _unique_values(marr['alt_min'])
232 1
		self.alts = _unique_values(marr['alt'])
233 1
		self.alts_max = _unique_values(marr['alt_max'])
234
235
		# unique latitudes
236 1
		self.lats_min = _unique_values(marr['lat_min'])
237 1
		self.lats = _unique_values(marr['lat'])
238 1
		self.lats_max = _unique_values(marr['lat_max'])
239
240
		# unique longitudes if available
241 1
		try:
242 1
			self.lons = _unique_values(marr['longitude'])
243
		except:
244
			pass
245
246 1
		self.nalt = len(self.alts)
247 1
		self.nlat = len(self.lats)
248 1
		self.nlon = len(self.lons)
249
250
		# reorder by latitude first, then altitude
251 1
		self.densities = marr['density'].flatten().reshape(self.nalt, self.nlat).transpose()
252 1
		self.dens_err_meas = marr['dens_err_meas'].flatten().reshape(self.nalt, self.nlat).transpose()
253 1
		self.dens_err_tot = marr['dens_err_tot'].flatten().reshape(self.nalt, self.nlat).transpose()
254 1
		self.dens_tot = marr['dens_tot'].flatten().reshape(self.nalt, self.nlat).transpose()
255
256
		# apriori if available
257 1
		try:
258 1
			self.apriori = marr['apriori'].flatten().reshape(self.nalt, self.nlat).transpose()
259
		except:
260
			pass
261
		# akdiag if available
262 1
		try:
263 1
			self.akdiag = marr['akdiag'].flatten().reshape(self.nalt, self.nlat).transpose()
264
		except:
265
			pass
266
267 1
	def write_to_textfile(self, filename):
268
		"""Write NO densities to ascii table files
269
270
		Parameters
271
		----------
272
		filename: str or file object or io.TextIOBase.buffer
273
			The filename or stream to write the data to. For writing to
274
			stdout in python 3, pass `sys.stdout.buffer`.
275
		"""
276
		if hasattr(filename, 'seek'):
277
			f = filename
278
		else:
279
			f = open(filename, 'w')
280
281
		header = "%5s %13s %12s %13s %14s  %12s %14s   %12s  %12s %12s %12s %12s" % ("GP_ID",
282
				"Max_Hoehe[km]", "Hoehe[km]", "Min_Hoehe[km]",
283
				"Max_Breite[°]", "Breite[°]", "Min_Breite[°]",
284
				"Laenge[°]",
285
				"Dichte[cm^-3]", "Fehler Mess[cm^-3]",
286
				"Fehler tot[cm^-3]", "Gesamtdichte[cm^-3]")
287
		if self.apriori is not None:
288
			header = header + " %12s" % ("apriori[cm^-3]",)
289
		if self.akdiag is not None:
290
			header = header + " %12s" % ("AKdiag",)
291
		print(header, file=f)
292
293
		oformat = "%5i  %+1.5E %+1.5E  %+1.5E  %+1.5E %+1.5E  %+1.5E  %+1.5E   %+1.5E       %+1.5E      %+1.5E        %+1.5E"
294
		oformata = "  %+1.5E"
295
296
		for i, a in enumerate(self.alts):
297
			for j, b in enumerate(self.lats):
298
				print(oformat % (i * self.nlat + j,
299
					self.alts_max[i], a, self.alts_min[i],
300
					self.lats_max[j], b, self.lats_min[j], self.lons[j],
301
					self.densities[j, i], self.dens_err_meas[j, i],
302
					self.dens_err_tot[j, i], self.dens_tot[j, i]),
303
					end="", file=f)
304
				if self.apriori is not None:
305
					print(" " + oformata % self.apriori[j, i], end="", file=f)
306
				if self.akdiag is not None:
307
					print(" " + oformata % self.akdiag[j, i], end="", file=f)
308
				print("", file=f)
309
310 1
	def write_to_netcdf(self, filename, close=True):
311
		"""Write NO densities to netcdf files
312
313
		This function has no stream, i.e. file object, support.
314
		Uses single precision floats (32 bit) to save space.
315
316
		Parameters
317
		----------
318
		filename: str
319
			The name of the file to write the data to.
320
		close: bool, optional
321
			Whether or not to close the file after writing.
322
			Setting to `False` enables appending further data
323
			to the same file.
324
			Default: True
325
326
		Returns
327
		-------
328
		Nothing if `close` is True. If `close` is False, returns either an
329
		`netCDF4.Dataset`,
330
		`scipy.io.netcdf.netcdf_file` or
331
		`pupynere.netcdf_file` instance depending on availability.
332
		"""
333 1
		alts_min_out = np.asarray(self.alts_min).reshape(self.nalt)
334 1
		alts_out = np.asarray(self.alts).reshape(self.nalt)
335 1
		alts_max_out = np.asarray(self.alts_max).reshape(self.nalt)
336
337 1
		lats_min_out = np.asarray(self.lats_min).reshape(self.nlat)
338 1
		lats_out = np.asarray(self.lats).reshape(self.nlat)
339 1
		lats_max_out = np.asarray(self.lats_max).reshape(self.nlat)
340
341 1
		ncf = netcdf_file(filename, 'w', **fmtargs)
342
343 1
		if self.version is not None:
344 1
			ncf.version = self.version
345 1
		if self.data_version is not None:
346 1
			ncf.L2_data_version = self.data_version
347
		#ncf.creation_time = dt.datetime.utcnow().replace(tzinfo=_UTC).strftime("%a %b %d %Y %H:%M:%S %z (%Z)")
348 1
		ncf.creation_time = dt.datetime.utcnow().strftime("%a %b %d %Y %H:%M:%S +00:00 (UTC)")
349 1
		ncf.author = "Firstname Lastname"
350
351
		# create netcdf file
352 1
		ncf.createDimension('altitude', self.nalt)
353 1
		ncf.createDimension('latitude', self.nlat)
354 1
		ncf.createDimension('time', None)
355
356 1
		forbit = ncf.createVariable('orbit', np.dtype('int32').char, ('time',))
357 1
		ftime = ncf.createVariable('time', np.dtype('int32').char, ('time',))
358
359 1
		falts_min = ncf.createVariable('alt_min', np.dtype('float32').char, ('altitude',))
360 1
		falts = ncf.createVariable('altitude', np.dtype('float32').char, ('altitude',))
361 1
		falts_max = ncf.createVariable('alt_max', np.dtype('float32').char, ('altitude',))
362 1
		flats_min = ncf.createVariable('lat_min', np.dtype('float32').char, ('latitude',))
363 1
		flats = ncf.createVariable('latitude', np.dtype('float32').char, ('latitude',))
364 1
		flats_max = ncf.createVariable('lat_max', np.dtype('float32').char, ('latitude',))
365
366 1
		falts_min.units = 'km'
367 1
		falts_min.positive = 'up'
368 1
		falts.units = 'km'
369 1
		falts.positive = 'up'
370 1
		falts_max.units = 'km'
371 1
		falts_max.positive = 'up'
372 1
		flats_min.units = 'degrees_north'
373 1
		flats.units = 'degrees_north'
374 1
		flats_max.units = 'degrees_north'
375
376 1
		forbit.units = '1'
377 1
		forbit.long_name = 'SCIAMACHY/Envisat orbit number'
378 1
		ftime.units = 'days since {0}'.format(self.date0.isoformat(sep=' '))
379 1
		ftime.standard_name = 'time'
380
381 1
		fdens = ncf.createVariable('density', np.dtype('float32').char, ('time', 'latitude', 'altitude'))
382 1
		fdens.units = 'cm^{-3}'
383 1
		fdens.standard_name = 'number_concentration_of_nitrogen_monoxide_molecules_in_air'
384 1
		fdens_err_meas = ncf.createVariable('error_meas', np.dtype('float32').char, ('time', 'latitude', 'altitude'))
385 1
		fdens_err_meas.units = 'cm^{-3}'
386 1
		fdens_err_meas.long_name = 'NO number density measurement error'
387 1
		fdens_err_tot = ncf.createVariable('error_tot', np.dtype('float32').char, ('time', 'latitude', 'altitude'))
388 1
		fdens_err_tot.units = 'cm^{-3}'
389 1
		fdens_err_tot.long_name = 'NO number density total error'
390 1
		fdens_tot = ncf.createVariable('density_air', np.dtype('float32').char, ('time', 'latitude', 'altitude'))
391 1
		fdens_tot.units = 'cm^{-3}'
392 1
		fdens_tot.long_name = 'approximate overall number concentration of air molecules (NRLMSIS-00)'
393
394 1
		ftime[:] = self.date
395 1
		forbit[:] = self.orbit
396
397 1
		falts_min[:] = alts_min_out
398 1
		falts[:] = alts_out
399 1
		falts_max[:] = alts_max_out
400 1
		flats_min[:] = lats_min_out
401 1
		flats[:] = lats_out
402 1
		flats_max[:] = lats_max_out
403
		# reorder by latitude first, then altitude
404 1
		fdens[0, :] = self.densities
405
		# reorder by latitude first, then altitude
406 1
		fdens_err_meas[0, :] = self.dens_err_meas
407 1
		fdens_err_tot[0, :] = self.dens_err_tot
408 1
		fdens_tot[0, :] = self.dens_tot
409
410
		# longitudes if they are available
411 1
		if self.nlon > 0:
412 1
			lons_out = np.asarray(self.lons).reshape(self.nlon)
413 1
			flons = ncf.createVariable('longitude', np.dtype('float32').char, ('time', 'latitude',))
414 1
			flons.units = 'degrees_east'
415 1
			flons[0, :] = lons_out
416
417 1
		if self.apriori is not None:
418 1
			fapriori = ncf.createVariable('apriori',
419
					np.dtype('float32').char, ('time', 'latitude', 'altitude'))
420 1
			fapriori.units = 'cm^{-3}'
421 1
			fapriori.long_name = 'apriori NO number density'
422 1
			fapriori[0, :] = self.apriori
423
424 1
		if self.akdiag is not None:
425 1
			fakdiag = ncf.createVariable('akm_diagonal',
426
					np.dtype('float32').char, ('time', 'latitude', 'altitude'))
427 1
			fakdiag.units = '1'
428 1
			fakdiag.long_name = 'averaging kernel matrix diagonal element'
429 1
			fakdiag[0, :] = self.akdiag
430 1
		if close:
431
			ncf.close()
432
		else:
433 1
			return ncf
434
435 1
	def read_from_netcdf(self, filename, close=True):
436
		"""Read NO densities from netcdf files
437
438
		This function has no stream, i.e. file object support.
439
440
		Parameters
441
		----------
442
		filename: str
443
			The filename to read the data from.
444
		close: bool, optional
445
			Whether or not to close the file after reading.
446
			Setting to `False` enables reading further data
447
			from the same file.
448
			Default: True
449
450
		Returns
451
		-------
452
		Nothing if `close` is True. If `close` is False, returns either an
453
		`netCDF4.Dataset`,
454
		`scipy.io.netcdf.netcdf_file` or
455
		`pupynere.netcdf_file` instance depending on availability.
456
		"""
457 1
		ncf = netcdf_file(filename, 'r')
458
459
		self.nalt = ncf.dimensions['altitude']
460
		self.nlat = ncf.dimensions['latitude']
461
462
		self.alts_min = ncf.variables['alt_min'][:]
463
		self.alts = ncf.variables['altitude'][:]
464
		self.alts_max = ncf.variables['alt_max'][:]
465
		self.lats_min = ncf.variables['lat_min'][:]
466
		self.lats = ncf.variables['latitude'][:]
467
		self.lats_max = ncf.variables['lat_max'][:]
468
469
		self.date = ncf.variable['time'][:]
470
		self.orbit = ncf.variable['orbit'][:]
471
472
		self.densities = ncf.variables['density'][:]
473
		self.dens_err_meas = ncf.variables['error_meas'][:]
474
		self.dens_err_tot = ncf.variables['error_tot'][:]
475
		self.dens_tot = ncf.variables['density_air'][:]
476
477
		# longitudes if they are available
478
		try:
479
			self.nlon = ncf.dimensions['longitude']
480
			self.lons = ncf.variables['longitude'][:]
481
		except:
482
			pass
483
484
		# apriori
485
		try:
486
			self.apriori = ncf.variables['apriori'][:]
487
		except:
488
			pass
489
490
		# akm diagonal elements
491
		try:
492
			self.akdiag = ncf.variables['akm_diagonal'][:]
493
		except:
494
			pass
495
496
		if close:
497
			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