Passed
Push — master ( 478ca5...fa390c )
by Stefan
03:58
created

sciapy.level2.density_pp   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 910
Duplicated Lines 95.71 %

Test Coverage

Coverage 72.29%

Importance

Changes 0
Metric Value
eloc 613
dl 871
loc 910
rs 2.6269
c 0
b 0
f 0
ccs 347
cts 480
cp 0.7229
wmc 72

10 Methods

Rating   Name   Duplication   Size   Complexity  
F scia_densities_pp.write_to_textfile() 86 86 30
A scia_densities_pp.__init__() 17 17 1
A scia_density_day.append() 29 29 1
F scia_densities_pp.write_to_netcdf() 106 106 14
A scia_densities_pp.to_xarray() 33 33 4
B scia_density_day.append_data() 56 56 3
B scia_density_day.to_xarray() 163 163 4
A scia_density_day.__init__() 32 32 1
B scia_density_day.write_to_netcdf() 181 181 4
C scia_densities_pp.read_from_netcdf() 57 57 10

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like sciapy.level2.density_pp often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
# vim:fileencoding=utf-8
3
#
4
# Copyright (c) 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 post-processed number densities interface
12
13
Interface classes for the level 2 post-processed retrieval results.
14
"""
15
16 1
from __future__ import absolute_import, division, print_function
17
18 1
import datetime as dt
19
20 1
import numpy as np
21 1
try:
22 1
	from netCDF4 import Dataset as netcdf_file
23 1
	fmtargs = {"format": "NETCDF4"}
24
except ImportError:
25
	try:
26
		from scipy.io.netcdf import netcdf_file
27
		fmtargs = {"version": 1}
28
	except ImportError:
29
		from pupynere import netcdf_file
30
		fmtargs = {"version": 1}
31
32 1
from .density import scia_densities, _UTC
33
34 1
__all__ = ["scia_densities_pp", "scia_density_day"]
35
36
37 1 View Code Duplication
class scia_densities_pp(scia_densities):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
38
	"""Post-processed SCIAMACHY number densities
39
40
	Extends `scia_densities` with additional post-processing
41
	attributes such as (MSIS) temperature and density, local
42
	solar time, solar zenith angle, and geomagnetic latitudes
43
	and longitudes.
44
45
	This class only supports writing ascii files but reading to
46
	and writing from netcdf.
47
48
	Attributes
49
	----------
50
	temperature
51
		NRLMSISE-00 temperatures
52
	noem_no
53
		NOEM NO nuimber densities
54
	vmr
55
		NO vmr using the NRLMSISE-00 total air number densities
56
	lst
57
		Apparent local solar times
58
	mst
59
		Mean local solar times
60
	sza
61
		Solar zenith angles
62
	utchour
63
		UTC hours into measurement day
64
	utcdays
65
		Number of days since reference date
66
	gmlats
67
		IGRF-12 geomagentic latitudes
68
	gmlons
69
		IGRF-12 geomagentic longitudes
70
	aacgmgmlats
71
		AACGM geomagentic latitudes
72
	aacgmgmlons
73
		AACGM geomagentic longitudes
74
75
	Methods
76
	-------
77
	write_to_textfile
78
	write_to_netcdf
79
	read_from_netcdf
80
	to_xarray
81
	"""
82 1
	def __init__(self, ref_date="2000-01-01",
83
			ver=None, data_ver=None):
84 1
		self.filename = None
85 1
		self.temperature = None
86 1
		self.noem_no = None
87 1
		self.vmr = None
88 1
		self.lst = None
89 1
		self.mst = None
90 1
		self.sza = None
91 1
		self.utchour = None
92 1
		self.utcdays = None
93 1
		self.gmlats = None
94 1
		self.gmlons = None
95 1
		self.aacgmgmlats = None
96 1
		self.aacgmgmlons = None
97 1
		super(scia_densities_pp, self).__init__(
98
				ref_date=ref_date, ver=ver, data_ver=data_ver)
99
100 1
	def write_to_textfile(self, filename):
101
		"""Write the variables to ascii table files
102
103
		Parameters
104
		----------
105
		filename: str or file object or io.TextIOBase.buffer
106
			The filename or stream to write the data to. For writing to
107
			stdout in python 3, pass `sys.stdout.buffer`.
108
		"""
109
		if hasattr(filename, 'seek'):
110
			f = filename
111
		else:
112
			f = open(filename, 'w')
113
114
		header = "%5s %13s %12s %13s %14s  %12s %14s   %12s  %12s %12s %12s %12s" % ("GP_ID",
115
				"Max_Hoehe[km]", "Hoehe[km]", "Min_Hoehe[km]",
116
				"Max_Breite[°]", "Breite[°]", "Min_Breite[°]",
117
				"Laenge[°]",
118
				"Dichte[cm^-3]", "Fehler Mess[cm^-3]",
119
				"Fehler tot[cm^-3]", "Gesamtdichte[cm^-3]")
120
		if self.apriori is not None:
121
			header = header + " %12s" % ("apriori[cm^-3]",)
122
		if self.temperature is not None:
123
			header = header + " %12s" % ("T[K]",)
124
		if self.noem_no is not None:
125
			header = header + " %12s" % ("NOEM_NO[cm^-3]",)
126
		if self.akdiag is not None:
127
			header = header + " %12s" % ("AKdiag",)
128
		if self.lst is not None:
129
			header = header + " %12s" % ("LST",)
130
		if self.mst is not None:
131
			header = header + " %12s" % ("MST",)
132
		if self.sza is not None:
133
			header = header + " %12s" % ("SZA",)
134
		if self.utchour is not None:
135
			header = header + " %12s" % ("Hour",)
136
		if self.utcdays is not None:
137
			header = header + " %12s" % ("Days",)
138
		if self.gmlats is not None:
139
			header = header + " %12s" % ("GeomagLat",)
140
		if self.gmlons is not None:
141
			header = header + " %12s" % ("GeomagLon",)
142
		if self.aacgmgmlats is not None:
143
			header = header + " %12s" % ("AACGMGeomagLat",)
144
		if self.aacgmgmlons is not None:
145
			header = header + " %12s" % ("AACGMGeomagLon",)
146
		print(header, file=f)
147
148
		oformat = "%5i  %+1.5E %+1.5E  %+1.5E  %+1.5E %+1.5E  %+1.5E  %+1.5E   %+1.5E       %+1.5E      %+1.5E        %+1.5E"
149
		oformata = "  %+1.5E"
150
151
		for i, a in enumerate(self.alts):
152
			for j, b in enumerate(self.lats):
153
				print(oformat % (i * self.nlat + j,
154
					self.alts_max[i], a, self.alts_min[i],
155
					self.lats_max[j], b, self.lats_min[j], self.lons[j],
156
					self.densities[j, i], self.dens_err_meas[j, i],
157
					self.dens_err_tot[j, i], self.dens_tot[j, i]),
158
					end="", file=f)
159
				if self.apriori is not None:
160
					print(" " + oformata % self.apriori[j, i], end="", file=f)
161
				if self.temperature is not None:
162
					print(" " + oformata % self.temperature[j, i], end="", file=f)
163
				if self.noem_no is not None:
164
					print(" " + oformata % self.noem_no[j, i], end="", file=f)
165
				if self.akdiag is not None:
166
					print(" " + oformata % self.akdiag[j, i], end="", file=f)
167
				if self.lst is not None:
168
					print(" " + oformata % self.lst[j], end="", file=f)
169
				if self.mst is not None:
170
					print(" " + oformata % self.mst[j], end="", file=f)
171
				if self.sza is not None:
172
					print(" " + oformata % self.sza[j], end="", file=f)
173
				if self.utchour is not None:
174
					print(" " + oformata % self.utchour[j], end="", file=f)
175
				if self.utcdays is not None:
176
					print(" " + oformata % self.utcdays[j], end="", file=f)
177
				if self.gmlats is not None:
178
					print(" " + oformata % self.gmlats[j], end="", file=f)
179
				if self.gmlons is not None:
180
					print(" " + oformata % self.gmlons[j], end="", file=f)
181
				if self.aacgmgmlats is not None:
182
					print(" " + oformata % self.aacgmgmlats[j], end="", file=f)
183
				if self.aacgmgmlons is not None:
184
					print(" " + oformata % self.aacgmgmlons[j], end="", file=f)
185
				print("", file=f)
186
187 1
	def write_to_netcdf(self, filename, close=True):
188
		"""Write variables to netcdf files
189
190
		This function has no stream, i.e. file object, support.
191
192
		Parameters
193
		----------
194
		filename: str
195
			The name of the file to write the data to.
196
		"""
197
		# write the base variables first and keep the file open for appending
198 1
		ncf = scia_densities.write_to_netcdf(self, filename, close=False)
199
200 1
		if self.temperature is not None:
201 1
			ftemp = ncf.createVariable('temperature',
202
					np.dtype('float64').char, ('time', 'latitude', 'altitude'))
203 1
			ftemp.units = 'K'
204 1
			ftemp.long_name = 'temperature'
205 1
			ftemp.model = 'NRLMSIS-00'
206 1
			ftemp[0, :] = self.temperature
207
208 1
		if self.noem_no is not None:
209 1
			fnoem_no = ncf.createVariable('NOEM_density',
210
					np.dtype('float64').char, ('time', 'latitude', 'altitude'))
211 1
			fnoem_no.units = 'cm^{-3}'
212 1
			fnoem_no.long_name = 'NOEM NO number density'
213 1
			fnoem_no[0, :] = self.noem_no
214
215 1
		if self.vmr is not None:
216 1
			fvmr = ncf.createVariable('VMR',
217
					np.dtype('float64').char, ('time', 'latitude', 'altitude'))
218 1
			fvmr.units = 'ppb'
219 1
			fvmr.long_name = 'volume mixing ratio'
220 1
			fvmr[0, :] = self.vmr
221
222 1
		if self.lst is not None:
223 1
			flst = ncf.createVariable('app_lst',
224
					np.dtype('float64').char, ('time', 'latitude',))
225 1
			flst.units = 'hours'
226 1
			flst.long_name = 'apparent local solar time'
227 1
			flst[0, :] = self.lst
228
229 1
		if self.mst is not None:
230 1
			fmst = ncf.createVariable('mean_lst',
231
					np.dtype('float64').char, ('time', 'latitude',))
232 1
			fmst.units = 'hours'
233 1
			fmst.long_name = 'mean local solar time'
234 1
			fmst[0, :] = self.mst
235
236 1
		if self.sza is not None:
237 1
			fsza = ncf.createVariable('mean_sza',
238
					np.dtype('float64').char, ('time', 'latitude',))
239 1
			fsza.units = 'degrees'
240 1
			fsza.long_name = 'mean solar zenith angle'
241 1
			fsza[0, :] = self.sza
242
243 1
		if self.utchour is not None:
244 1
			futc = ncf.createVariable('utc_hour',
245
					np.dtype('float64').char, ('time', 'latitude',))
246 1
			futc.units = 'hours'
247 1
			futc.long_name = 'measurement utc time'
248 1
			futc[0, :] = self.utchour
249
250 1
		if self.utcdays is not None:
251 1
			futcd = ncf.createVariable('utc_days',
252
					np.dtype('float64').char, ('time', 'latitude',))
253 1
			futcd.long_name = 'measurement day'
254 1
			futcd.units = 'days since {0}'.format(self.date0.isoformat(sep=' '))
255 1
			futcd[0, :] = self.utcdays
256
257 1
		if self.gmlats is not None:
258 1
			fgmlats = ncf.createVariable('gm_lats',
259
					np.dtype('float64').char, ('time', 'latitude',))
260 1
			fgmlats.long_name = 'geomagnetic_latitude'
261 1
			fgmlats.model = 'IGRF'
262 1
			fgmlats.units = 'degrees_north'
263 1
			fgmlats[0, :] = self.gmlats
264
265 1
		if self.gmlons is not None:
266 1
			fgmlons = ncf.createVariable('gm_lons',
267
					np.dtype('float64').char, ('time', 'latitude',))
268 1
			fgmlons.long_name = 'geomagnetic_longitude'
269 1
			fgmlons.model = 'IGRF'
270 1
			fgmlons.units = 'degrees_east'
271 1
			fgmlons[0, :] = self.gmlons
272
273 1
		if self.aacgmgmlats is not None:
274 1
			faacgmgmlats = ncf.createVariable('aacgm_gm_lats',
275
					np.dtype('float64').char, ('time', 'latitude',))
276 1
			faacgmgmlats.long_name = 'geomagnetic_latitude'
277 1
			faacgmgmlats.model = 'AACGM'
278 1
			faacgmgmlats.units = 'degrees_north'
279 1
			faacgmgmlats[0, :] = self.aacgmgmlats
280
281 1
		if self.aacgmgmlons is not None:
282 1
			faacgmgmlons = ncf.createVariable('aacgm_gm_lons',
283
					np.dtype('float64').char, ('time', 'latitude',))
284 1
			faacgmgmlons.long_name = 'geomagnetic_longitude'
285 1
			faacgmgmlons.model = 'AACGM'
286 1
			faacgmgmlons.units = 'degrees_east'
287 1
			faacgmgmlons[0, :] = self.aacgmgmlons
288
289 1
		if close:
290 1
			ncf.close()
291
		else:
292
			return ncf
293
294 1
	def read_from_netcdf(self, filename, close=True):
295
		"""Read post-processed level 2 orbit files
296
297
		Parameters
298
		----------
299
		filename: str
300
			The name of the netcdf file.
301
		"""
302
		# read the base variables first and keep the file open for reading
303 1
		ncf = scia_densities.read_from_netcdf(self, filename, close=False)
304
305
		# additional data...
306
		# MSIS temperature
307
		try:
308
			self.temperature = ncf.variables['temperature'][:]
309
		except:
310
			pass
311
		# NOEM density
312
		try:
313
			self.noem_no = ncf.variables['NOEM_density'][:]
314
		except:
315
			pass
316
		# calculated vmr
317
		try:
318
			self.vmr = ncf.variables['VMR'][:]
319
		except:
320
			pass
321
		# apparent local solar time
322
		try:
323
			self.lst = ncf.variables['app_lst'][:]
324
		except:
325
			pass
326
		# mean local solar time
327
		try:
328
			self.mst = ncf.variables['mean_lst'][:]
329
		except:
330
			pass
331
		# mean solar zenith angle
332
		try:
333
			self.sza = ncf.variables['mean_sza'][:]
334
		except:
335
			pass
336
		# utc hours
337
		try:
338
			self.utchour = ncf.variables['utc_hour'][:]
339
		except:
340
			pass
341
		# utc days
342
		try:
343
			self.utcdays = ncf.variables['utc_days'][:]
344
		except:
345
			pass
346
347
		if close:
348
			ncf.close()
349
		else:
350
			return ncf
351
352 1
	def to_xarray(self, dateo, orbit):
353
		"""Convert the data to :class:`xarray.Dataset`
354
355
		This is a very simple approach, it dumps the data to a temporary
356
		netcdf file and reads that using :func:`xarray.open_dataset()`.
357
358
		Parameters
359
		----------
360
		dateo: float
361
			The days since the reference date at the equator
362
			crossing of the orbit. Used to set the `time`
363
			dimension of the dataset.
364
		orbit: int
365
			The SCIAMACHY/Envisat orbit number of the retrieved data.
366
367
		Returns
368
		-------
369
		dataset: xarray.Dataset
370
		"""
371 1
		import tempfile
372 1
		try:
373 1
			import xarray as xr
374
		except ImportError:
375
			print("Error: xarray not available!")
376
			return None
377 1
		with tempfile.NamedTemporaryFile() as tf:
378 1
			self.write_to_netcdf(tf.name)
379 1
			with xr.open_dataset(tf.name, decode_cf=False) as sdorb:
380 1
				sdorb = sdorb.drop(["alt_min", "alt_max", "lat_min", "lat_max"])
381 1
				sdorb["time"] = np.array([dateo], dtype=np.float64)
382 1
				sdorb["orbit"] = orbit
383 1
				sdorb.load()
384 1
		return sdorb
385
386
387 1 View Code Duplication
class scia_density_day(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
388
	"""SCIAMACHY daily number densities combined
389
390
	Contains a stacked version of the post-processed orbit data
391
	for multiple orbits on a day. Used to combine the results.
392
393
	Parameters
394
	----------
395
	name: str
396
		Name of the retrieved species, default: "NO".
397
		Used to name the netcdf variables appropriately.
398
	ref_date: str, optional
399
		The reference date on which to base the date calculations on.
400
		Default: "2000-01-01"
401
402
	Attributes
403
	----------
404
	alts
405
		Retrieval grid altitudes
406
	lats
407
		Retrieval grid latitudes
408
	no_dens
409
		Retrieved number densities
410
	no_errs
411
		Measurement uncertainty
412
	no_etot
413
		Total uncertainty
414
	no_rstd
415
		Relative measurement uncertainty
416
	no_akd
417
		Averaging kernel diagonal elements
418
	no_apri
419
		Prior number density
420
	temperature
421
		NRLMSISE-00 temperatures
422
	noem_no
423
		NOEM NO nuimber densities
424
	vmr
425
		NO vmr using the NRLMSISE-00 total air number densities
426
	lst
427
		Apparent local solar times
428
	mst
429
		Mean local solar times
430
	sza
431
		Solar zenith angles
432
	utchour
433
		UTC hours into measurement day
434
	utcdays
435
		Number of days since reference date
436
	gmlats
437
		IGRF-12 geomagentic latitudes
438
	gmlons
439
		IGRF-12 geomagentic longitudes
440
	aacgmgmlats
441
		AACGM geomagentic latitudes
442
	aacgmgmlons
443
		AACGM geomagentic longitudes
444
	"""
445 1
	def __init__(self, name="NO", ref_date="2000-01-01", author="unknown"):
446 1
		self.date0 = (dt.datetime.strptime(ref_date, "%Y-%m-%d")
447
					.replace(tzinfo=_UTC))
448 1
		self.alts = None
449 1
		self.lats = None
450 1
		self.version = None
451 1
		self.data_version = None
452 1
		self.name = name
453 1
		self.author = author
454 1
		self.date = []
455 1
		self.time = []
456 1
		self.orbit = []
457 1
		self.no_dens = None
458 1
		self.no_errs = None
459 1
		self.no_etot = None
460 1
		self.no_rstd = None
461 1
		self.no_akd = None
462 1
		self.no_apri = None
463 1
		self.no_noem = None
464 1
		self.temperature = None
465 1
		self.tot_dens = None
466 1
		self.no_vmr = None
467 1
		self.lons = None
468 1
		self.lst = None
469 1
		self.mst = None
470 1
		self.sza = None
471 1
		self.utchour = None
472 1
		self.utcdays = None
473 1
		self.gmlats = None
474 1
		self.gmlons = None
475 1
		self.aacgmgmlats = None
476 1
		self.aacgmgmlons = None
477
478 1
	def append(self, cdata):
479
		"""Append (stack) the data from one orbit
480
481
		Parameters
482
		----------
483
		cdata: :class:`scia_densities_pp` instance
484
			Post-processed level 2 orbital data.
485
		"""
486
		self.time.extend(cdata.time)
487
		self.date.extend(cdata.date)
488
		self.no_dens = np.ma.dstack((self.no_dens, cdata.no_dens))
489
		self.no_errs = np.ma.dstack((self.no_errs, cdata.no_errs))
490
		self.no_etot = np.ma.dstack((self.no_etot, cdata.no_etot))
491
		self.no_rstd = np.ma.dstack((self.no_rstd, cdata.no_rstd))
492
		self.no_akd = np.ma.dstack((self.no_akd, cdata.no_akd))
493
		self.no_apri = np.ma.dstack((self.no_apri, cdata.no_apri))
494
		self.no_noem = np.ma.dstack((self.no_noem, cdata.no_noem))
495
		self.tot_dens = np.ma.dstack((self.tot_dens, cdata.tot_dens))
496
		self.no_vmr = np.ma.dstack((self.no_vmr, cdata.no_vmr))
497
		self.lons = np.ma.dstack((self.lons, cdata.lons))
498
		self.lst = np.ma.dstack((self.lst, cdata.lst))
499
		self.mst = np.ma.dstack((self.mst, cdata.mst))
500
		self.sza = np.ma.dstack((self.sza, cdata.sza))
501
		self.utchour = np.ma.dstack((self.utchour, cdata.utchour))
502
		self.utcdays = np.ma.dstack((self.utcdays, cdata.utcdays))
503
		self.gmlats = np.ma.dstack((self.gmlats, cdata.gmlats))
504
		self.gmlons = np.ma.dstack((self.gmlons, cdata.gmlons))
505
		self.aacgmgmlats = np.ma.dstack((self.aacgmgmlats, cdata.aacgmgmlats))
506
		self.aacgmgmlons = np.ma.dstack((self.aacgmgmlons, cdata.aacgmgmlons))
507
508 1
	def append_data(self, date, orbit, equtime, scia_dens):
509
		"""Append (stack) the data from one orbit
510
511
		Updates the data in place.
512
513
		Parameters
514
		----------
515
		date: float
516
			Days since `ref_date` for the time coordinate
517
		orbit: int
518
			SCIAMACHY/Envisat orbit number
519
		equtime: float
520
			UTC hour into the day at the equator
521
		scia_dens: :class:`scia_densities_pp` instance
522
			The post-processed orbit data set
523
		"""
524 1
		def _vstack_or_new(a, b):
525
			# Check if we 'stack' for the first time (a is None),
526
			# in that case we assign first.
527 1
			if a is None:
528 1
				return b[None]
529 1
			return np.ma.vstack((a, b[None]))
530
531 1
		self.version = scia_dens.version
532 1
		self.data_version = scia_dens.data_version
533 1
		self.date.append(date)
534 1
		self.orbit.append(orbit)
535 1
		self.time.append(equtime)
536 1
		_dens = scia_dens.densities
537 1
		_errs = scia_dens.dens_err_meas
538 1
		_etot = scia_dens.dens_err_tot
539 1
		_r_std = np.abs(_errs / _dens) * 100.0
540 1
		if self.alts is None:
541
			# we need altitudes and latitudes only once
542 1
			self.alts = scia_dens.alts
543 1
			self.lats = scia_dens.lats
544 1
		self.no_dens = _vstack_or_new(self.no_dens, _dens)
545 1
		self.no_errs = _vstack_or_new(self.no_errs, _errs)
546 1
		self.no_etot = _vstack_or_new(self.no_etot, _etot)
547 1
		self.no_rstd = _vstack_or_new(self.no_rstd, _r_std)
548 1
		self.no_akd = _vstack_or_new(self.no_akd, scia_dens.akdiag)
549 1
		self.no_apri = _vstack_or_new(self.no_apri, scia_dens.apriori)
550 1
		self.temperature = _vstack_or_new(self.temperature, scia_dens.temperature)
551 1
		self.no_noem = _vstack_or_new(self.no_noem, scia_dens.noem_no)
552 1
		self.tot_dens = _vstack_or_new(self.tot_dens, scia_dens.dens_tot)
553 1
		self.no_vmr = _vstack_or_new(self.no_vmr, scia_dens.vmr)
554 1
		self.lons = _vstack_or_new(self.lons, scia_dens.lons)
555 1
		self.lst = _vstack_or_new(self.lst, scia_dens.lst)
556 1
		self.mst = _vstack_or_new(self.mst, scia_dens.mst)
557 1
		self.sza = _vstack_or_new(self.sza, scia_dens.sza)
558 1
		self.utchour = _vstack_or_new(self.utchour, scia_dens.utchour)
559 1
		self.utcdays = _vstack_or_new(self.utcdays, scia_dens.utcdays)
560 1
		self.gmlats = _vstack_or_new(self.gmlats, scia_dens.gmlats)
561 1
		self.gmlons = _vstack_or_new(self.gmlons, scia_dens.gmlons)
562 1
		self.aacgmgmlats = _vstack_or_new(self.aacgmgmlats, scia_dens.aacgmgmlats)
563 1
		self.aacgmgmlons = _vstack_or_new(self.aacgmgmlons, scia_dens.aacgmgmlons)
564
565 1
	def write_to_netcdf(self, filename):
566
		"""Write variables to netcdf files
567
568
		Parameters
569
		----------
570
		filename: str
571
			The name of the file to write the data to.
572
		"""
573 1
		_var_dicts = {
574
			"2.1": {
575
				"dens_tot": {
576
					"name": "TOT_DENS",
577
					"long_name": "total number density (NRLMSIS-00)",
578
					"model": None,
579
				},
580
				"temperature": {
581
					"name": "temperature",
582
					"long_name": "temperature",
583
					"model": "NRLMSIS-00",
584
				},
585
			},
586
			"2.2": {
587
				"dens_tot": {
588
					"name": "MSIS_Dens",
589
					"long_name": "MSIS total number density",
590
					"model": "NRLMSIS-00",
591
				},
592
				"temperature": {
593
					"name": "MSIS_Temp",
594
					"long_name": "MSIS temperature",
595
					"model": "NRLMSIS-00",
596
				},
597
			},
598
		}
599
600 1
		ncf = netcdf_file(filename, 'w', **fmtargs)
601 1
		o = np.asarray(self.orbit)
602 1
		d = np.asarray(self.date)
603 1
		t = np.asarray(self.time)
604
605 1
		if self.version is not None:
606 1
			ncf.version = self.version
607 1
		if self.data_version is not None:
608 1
			ncf.L2_data_version = self.data_version
609 1
		ncf.creation_time = dt.datetime.utcnow().strftime("%a %b %d %Y %H:%M:%S +00:00 (UTC)")
610 1
		ncf.author = self.author
611
612 1
		ncf.createDimension('time', None)
613 1
		ncf.createDimension('altitude', self.alts.size)
614 1
		ncf.createDimension('latitude', self.lats.size)
615 1
		forbit = ncf.createVariable('orbit', np.dtype('int32').char, ('time',))
616 1
		forbit.axis = 'T'
617 1
		forbit.calendar = 'standard'
618 1
		forbit.long_name = 'orbit'
619 1
		forbit.standard_name = 'orbit'
620 1
		forbit.units = 'orbit number'
621
		# the time coordinate is actually called "date" here
622 1
		ftime = ncf.createVariable('time', 'f8', ('time',))
623 1
		ftime.axis = 'T'
624 1
		ftime.calendar = 'standard'
625 1
		ftime.long_name = 'equatorial crossing time'
626 1
		ftime.standard_name = 'time'
627 1
		ftime.units = 'days since {0}'.format(self.date0.isoformat(sep=' '))
628
		#ftime.units = 'days since {0}'.format(self.date0.strftime('%Y-%m-%d %H:%M:%S%z (%Z)'))
629
		#fdate = ncf.createVariable('date', np.dtype('float64').char, ('time',))
630
		#fdate.axis = 'T'
631
		#fdate.calendar = 'standard'
632
		#fdate.long_name = 'date'
633
		#fdate.standard_name = 'date'
634
		#fdate.units = 'days since 1950-01-01 00:00:00'
635 1
		falts = ncf.createVariable('altitude', 'f8', ('altitude',))
636 1
		falts.axis = 'Z'
637 1
		falts.long_name = 'altitude'
638 1
		falts.standard_name = 'altitude'
639 1
		falts.units = 'km'
640 1
		falts.positive = 'up'
641 1
		flats = ncf.createVariable('latitude', 'f8', ('latitude',))
642 1
		flats.axis = 'Y'
643 1
		flats.long_name = 'latitude'
644 1
		flats.standard_name = 'latitude'
645 1
		flats.units = 'degrees_north'
646 1
		flons = ncf.createVariable('longitude', 'f8', ('time', 'latitude',))
647 1
		flons.long_name = 'longitude'
648 1
		flons.standard_name = 'longitude'
649 1
		flons.units = 'degrees_east'
650 1
		fdens = ncf.createVariable('%s_DENS' % self.name, 'f8', ('time', 'latitude', 'altitude'))
651 1
		fdens.units = 'cm^{-3}'
652 1
		fdens.long_name = '%s number density' % self.name
653 1
		ferrs = ncf.createVariable('%s_ERR' % self.name, 'f8', ('time', 'latitude', 'altitude'))
654 1
		ferrs.units = 'cm^{-3}'
655 1
		ferrs.long_name = '%s density measurement error' % self.name
656 1
		fetot = ncf.createVariable('%s_ETOT' % self.name, 'f8', ('time', 'latitude', 'altitude'))
657 1
		fetot.units = 'cm^{-3}'
658 1
		fetot.long_name = '%s density total error' % self.name
659 1
		frstd = ncf.createVariable('%s_RSTD' % self.name, 'f8', ('time', 'latitude', 'altitude'))
660 1
		frstd.units = '%'
661 1
		frstd.long_name = '%s relative standard deviation' % self.name
662 1
		fakd = ncf.createVariable('%s_AKDIAG' % self.name, 'f8', ('time', 'latitude', 'altitude'))
663 1
		fakd.units = '1'
664 1
		fakd.long_name = '%s averaging kernel diagonal element' % self.name
665 1
		fapri = ncf.createVariable('%s_APRIORI' % self.name, 'f8', ('time', 'latitude', 'altitude'))
666 1
		fapri.units = 'cm^{-3}'
667 1
		fapri.long_name = '%s apriori density' % self.name
668 1
		ftemp = ncf.createVariable(_var_dicts[self.version]["temperature"]["name"],
669
				'f8', ('time', 'latitude', 'altitude'))
670 1
		ftemp.long_name = _var_dicts[self.version]["temperature"]["long_name"]
671 1
		ftemp.model = 'NRLMSIS-00'
672 1
		ftemp.units = 'K'
673 1
		fdens_tot = ncf.createVariable(_var_dicts[self.version]["dens_tot"]["name"],
674
				'f8', ('time', 'latitude', 'altitude'))
675 1
		fdens_tot.long_name = _var_dicts[self.version]["dens_tot"]["long_name"]
676 1
		fdens_tot.units = 'cm^{-3}'
677 1
		if _var_dicts[self.version]["dens_tot"]["model"] is not None:
678 1
			fdens_tot.model = _var_dicts[self.version]["dens_tot"]["model"]
679 1
		fnoem = ncf.createVariable('%s_NOEM' % self.name, 'f8', ('time', 'latitude', 'altitude'))
680 1
		fnoem.units = 'cm^{-3}'
681 1
		fnoem.long_name = 'NOEM %s number density' % self.name
682 1
		fvmr = ncf.createVariable('%s_VMR' % self.name, 'f8', ('time', 'latitude', 'altitude'))
683 1
		fvmr.units = 'ppb'
684 1
		fvmr.long_name = '%s volume mixing ratio' % self.name
685 1
		flst = ncf.createVariable('app_LST', 'f8', ('time', 'latitude'))
686 1
		flst.units = 'hours'
687 1
		flst.long_name = 'apparent local solar time'
688 1
		fmst = ncf.createVariable('mean_LST', 'f8', ('time', 'latitude'))
689 1
		fmst.units = 'hours'
690 1
		fmst.long_name = 'mean local solar time'
691 1
		fsza = ncf.createVariable('mean_SZA', 'f8', ('time', 'latitude'))
692 1
		fsza.units = 'degrees'
693 1
		fsza.long_name = 'solar zenith angle at mean altitude'
694 1
		futc = ncf.createVariable('UTC', 'f8', ('time', 'latitude'))
695 1
		futc.units = 'hours'
696 1
		futc.long_name = 'measurement utc time'
697 1
		futcd = ncf.createVariable('utc_days', 'f8', ('time', 'latitude'))
698 1
		futcd.long_name = 'measurement utc day'
699 1
		futcd.units = 'days since {0}'.format(self.date0.isoformat(sep=' '))
700
701 1
		fgmlats = ncf.createVariable('gm_lats', 'f8', ('time', 'latitude',))
702 1
		fgmlats.long_name = 'geomagnetic_latitude'
703 1
		fgmlats.model = 'IGRF'
704 1
		fgmlats.units = 'degrees_north'
705
706 1
		fgmlons = ncf.createVariable('gm_lons', 'f8', ('time', 'latitude',))
707 1
		fgmlons.long_name = 'geomagnetic_longitude'
708 1
		fgmlons.model = 'IGRF'
709 1
		fgmlons.units = 'degrees_east'
710
711 1
		faacgmgmlats = ncf.createVariable('aacgm_gm_lats', 'f8', ('time', 'latitude',))
712 1
		faacgmgmlats.long_name = 'geomagnetic_latitude'
713 1
		faacgmgmlats.model = 'AACGM'
714 1
		faacgmgmlats.units = 'degrees_north'
715
716 1
		faacgmgmlons = ncf.createVariable('aacgm_gm_lons', 'f8', ('time', 'latitude',))
717 1
		faacgmgmlons.long_name = 'geomagnetic_longitude'
718 1
		faacgmgmlons.model = 'AACGM'
719 1
		faacgmgmlons.units = 'degrees_east'
720
721 1
		forbit[:] = o
722 1
		ftime[:] = d
723 1
		falts[:] = self.alts
724 1
		flats[:] = self.lats
725 1
		flons[:] = self.lons
726 1
		fdens[:] = np.ma.atleast_3d(self.no_dens[:])
727 1
		ferrs[:] = np.ma.atleast_3d(self.no_errs[:])
728 1
		fetot[:] = np.ma.atleast_3d(self.no_etot[:])
729 1
		frstd[:] = np.ma.atleast_3d(self.no_rstd[:])
730 1
		fakd[:] = np.ma.atleast_3d(self.no_akd[:])
731 1
		fapri[:] = np.ma.atleast_3d(self.no_apri[:])
732 1
		ftemp[:] = np.ma.atleast_3d(self.temperature[:])
733 1
		fnoem[:] = np.ma.atleast_3d(self.no_noem[:])
734 1
		fdens_tot[:] = np.ma.atleast_3d(self.tot_dens[:])
735 1
		fvmr[:] = np.ma.atleast_3d(self.no_vmr[:])
736 1
		flst[:] = np.ma.atleast_2d(self.lst[:])
737 1
		fmst[:] = np.ma.atleast_2d(self.mst[:])
738 1
		fsza[:] = np.ma.atleast_2d(self.sza[:])
739 1
		futc[:] = np.ma.atleast_2d(self.utchour[:])
740 1
		futcd[:] = np.ma.atleast_2d(self.utcdays[:])
741 1
		fgmlats[:] = np.ma.atleast_2d(self.gmlats[:])
742 1
		fgmlons[:] = np.ma.atleast_2d(self.gmlons[:])
743 1
		faacgmgmlats[:] = np.ma.atleast_2d(self.aacgmgmlats[:])
744 1
		faacgmgmlons[:] = np.ma.atleast_2d(self.aacgmgmlons[:])
745 1
		ncf.close()
746
747 1
	def to_xarray(self):
748
		"""Convert the combined orbit data to :class:`xarray.Dataset`
749
750
		Exports the data using the same data variables as
751
		when writing to netcdf.
752
753
		Returns
754
		-------
755
		dataset: xarray.Dataset
756
		"""
757 1
		try:
758 1
			import xarray as xr
759
		except ImportError:
760
			print("Error: xarray not available!")
761
			return None
762 1
		o = np.asarray(self.orbit)
763 1
		d = np.asarray(self.date)
764
765 1
		xr_dens = xr.DataArray(self.no_dens, coords=[d, self.lats, self.alts],
766
				dims=["time", "latitude", "altitude"],
767
				attrs={"units": "cm^{-3}",
768
						"long_name": "{0} number density".format(self.name)},
769
				name="{0}_DENS".format(self.name))
770
771 1
		xr_errs = xr.DataArray(self.no_errs, coords=[d, self.lats, self.alts],
772
				dims=["time", "latitude", "altitude"],
773
				attrs={"units": "cm^{-3}",
774
						"long_name": "{0} density measurement error".format(self.name)},
775
				name="{0}_ERR".format(self.name))
776
777 1
		xr_etot = xr.DataArray(self.no_etot, coords=[d, self.lats, self.alts],
778
				dims=["time", "latitude", "altitude"],
779
				attrs={"units": "cm^{-3}",
780
						"long_name": "{0} density total error".format(self.name)},
781
				name="{0}_ETOT".format(self.name))
782
783 1
		xr_rstd = xr.DataArray(self.no_rstd, coords=[d, self.lats, self.alts],
784
				dims=["time", "latitude", "altitude"],
785
				attrs=dict(units='%',
786
						long_name='{0} relative standard deviation'.format(self.name)),
787
				name="{0}_RSTD".format(self.name))
788
789 1
		xr_akd = xr.DataArray(self.no_akd, coords=[d, self.lats, self.alts],
790
				dims=["time", "latitude", "altitude"],
791
				attrs=dict(units='1',
792
						long_name='{0} averaging kernel diagonal element'.format(self.name)),
793
				name="{0}_AKDIAG".format(self.name))
794
795 1
		xr_apri = xr.DataArray(self.no_apri, coords=[d, self.lats, self.alts],
796
				dims=["time", "latitude", "altitude"],
797
				attrs=dict(units='cm^{-3}', long_name='{0} apriori density'.format(self.name)),
798
				name="{0}_APRIORI".format(self.name))
799
800 1
		xr_noem = xr.DataArray(self.no_noem, coords=[d, self.lats, self.alts],
801
				dims=["time", "latitude", "altitude"],
802
				attrs=dict(units='cm^{-3}', long_name='NOEM {0} number density'.format(self.name)),
803
				name="{0}_NOEM".format(self.name))
804
805 1
		xr_vmr = xr.DataArray(self.no_vmr, coords=[d, self.lats, self.alts],
806
				dims=["time", "latitude", "altitude"],
807
				attrs=dict(units='ppb', long_name='{0} volume mixing ratio'.format(self.name)),
808
				name="{0}_VMR".format(self.name))
809
810 1
		xr_dtot = xr.DataArray(self.tot_dens, coords=[d, self.lats, self.alts],
811
				dims=["time", "latitude", "altitude"],
812
				attrs=dict(units='cm^{-3}', long_name='MSIS total number density',
813
					model='NRLMSIS-00'),
814
				name="MSIS_Dens")
815
816 1
		xr_temp = xr.DataArray(self.temperature, coords=[d, self.lats, self.alts],
817
				dims=["time", "latitude", "altitude"],
818
				attrs=dict(units='K', long_name='MSIS temperature',
819
						model="NRLMSIS-00"),
820
				name="MSIS_Temp")
821
822 1
		xr_lons = xr.DataArray(self.lons, coords=[d, self.lats],
823
				dims=["time", "latitude"],
824
				attrs=dict(long_name='longitude', standard_name='longitude',
825
						units='degrees_east'),
826
				name='longitude')
827
828 1
		xr_lst = xr.DataArray(self.lst, coords=[d, self.lats],
829
				dims=["time", "latitude"],
830
				attrs=dict(units='hours', long_name='apparent local solar time'),
831
				name="app_LST")
832
833 1
		xr_mst = xr.DataArray(self.mst, coords=[d, self.lats],
834
				dims=["time", "latitude"],
835
				attrs=dict(units='hours', long_name='mean local solar time'),
836
				name="mean_LST")
837
838 1
		xr_sza = xr.DataArray(self.sza, coords=[d, self.lats],
839
				dims=["time", "latitude"],
840
				attrs=dict(units='degrees',
841
						long_name='solar zenith angle at mean altitude'),
842
				name="mean_SZA")
843
844 1
		xr_utch = xr.DataArray(self.utchour, coords=[d, self.lats],
845
				dims=["time", "latitude"],
846
				attrs=dict(units='hours',
847
						long_name='measurement utc time'),
848
				name="UTC")
849
850 1
		xr_utcd = xr.DataArray(self.utcdays, coords=[d, self.lats],
851
				dims=["time", "latitude"],
852
				attrs=dict(units='days since {0}'.format(self.date0.isoformat(sep=' ')),
853
						long_name='measurement utc day'),
854
				name="utc_days")
855
856 1
		xr_gmlats = xr.DataArray(self.gmlats, coords=[d, self.lats],
857
				dims=["time", "latitude"],
858
				attrs=dict(long_name='geomagnetic_latitude',
859
						model='IGRF', units='degrees_north'),
860
				name="gm_lats")
861
862 1
		xr_gmlons = xr.DataArray(self.gmlons, coords=[d, self.lats],
863
				dims=["time", "latitude"],
864
				attrs=dict(long_name='geomagnetic_longitude',
865
						model='IGRF', units='degrees_east'),
866
				name="gm_lons")
867
868 1
		xr_aacgmgmlats = xr.DataArray(self.aacgmgmlats, coords=[d, self.lats],
869
				dims=["time", "latitude"],
870
				attrs=dict(long_name='geomagnetic_latitude',
871
						model='AACGM', units='degrees_north'),
872
				name="aacgm_gm_lats")
873
874 1
		xr_aacgmgmlons = xr.DataArray(self.aacgmgmlons, coords=[d, self.lats],
875
				dims=["time", "latitude"],
876
				attrs=dict(long_name='geomagnetic_longitude',
877
						model='AACGM', units='degrees_east'),
878
				name="aacgm_gm_lons")
879
880 1
		xr_orbit = xr.DataArray(o, coords=[d], dims=["time"],
881
				attrs=dict(axis='T', calendar='standard', long_name='orbit',
882
						standard_name='orbit', units='orbit number'),
883
				name="orbit")
884
885 1
		xr_ds = xr.Dataset({da.name: da for da in
886
				[xr_dens, xr_errs, xr_etot, xr_rstd, xr_akd, xr_apri, xr_noem,
887
					xr_vmr, xr_dtot, xr_temp, xr_lons, xr_lst, xr_mst, xr_sza,
888
					xr_utch, xr_utcd, xr_gmlats, xr_gmlons, xr_aacgmgmlats,
889
					xr_aacgmgmlons, xr_orbit]})
890
891 1
		xr_ds["time"].attrs = dict(axis='T', calendar='standard',
892
						long_name='equatorial crossing time',
893
						standard_name='time',
894
						units='days since {0}'.format(self.date0.isoformat(sep=' ')))
895
896 1
		xr_ds["altitude"].attrs = dict(axis='Z', long_name='altitude',
897
				standard_name='altitude', units='km', positive='up')
898
899 1
		xr_ds["latitude"].attrs = dict(axis='Y', long_name='latitude',
900
				standard_name='latitude', units='degrees_north')
901
902 1
		if self.version is not None:
903 1
			xr_ds.attrs["version"] = self.version
904 1
		if self.data_version is not None:
905 1
			xr_ds.attrs["L2_data_version"] = self.data_version
906 1
		xr_ds.attrs["creation_time"] = dt.datetime.utcnow().strftime("%a %b %d %Y %H:%M:%S +00:00 (UTC)")
907 1
		xr_ds.attrs["author"] = self.author
908
909
		return xr_ds
910