Passed
Push — master ( e8dfc7...897815 )
by Stefan
07:20
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 1
CRAP Score 1.125

Importance

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