sciapy.level1c.scia_limb   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 407
Duplicated Lines 0 %

Test Coverage

Coverage 42.86%

Importance

Changes 0
Metric Value
eloc 206
dl 0
loc 407
ccs 84
cts 196
cp 0.4286
rs 10
c 0
b 0
f 0
wmc 14

7 Methods

Rating   Name   Duplication   Size   Complexity  
A scia_limb_scan.__init__() 0 17 1
A scia_limb_scan.local_solar_time() 0 30 3
A scia_limb_scan.parse_textheader() 0 36 1
A scia_limb_scan.read_from_file() 0 16 3
A scia_limb_point.from_limb_scan() 0 35 2
A scia_limb_point.__init__() 0 26 1
B scia_limb_scan.assemble_textheader() 0 72 2

1 Function

Rating   Name   Duplication   Size   Complexity  
A _equation_of_time() 0 24 1
1
# -*- coding: utf-8 -*-
2
# vim:fileencoding=utf-8
3
#
4
# Copyright (c) 2014-2018 Stefan Bender
5
#
6
# This module is part of sciapy.
7
# sciapy is free software: you can redistribute it or modify
8
# it under the terms of the GNU General Public License as published
9
# by 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 1c limb spectra module
12
13
This module contains the class for SCIAMACHY level 1c limb spectra.
14
It include some simple conversion routines: from and to ascii,
15
from and to binary, from and to netcdf.
16
17
A simple import from HDF5 files produced by the SRON nadc_tools
18
(https://github.com/rmvanhees/nadc_tools) is also supported.
19
"""
20 1
from __future__ import absolute_import, division, print_function
21
22 1
import numpy as np
23
24 1
__all__ = ["scia_limb_point", "scia_limb_scan"]
25
26 1
def _equation_of_time(doy):
27
	"""Equation of time correction for day of year (doy)
28
29
	See:
30
	https://en.wikipedia.org/wiki/Equation_of_time
31
32
	Parameters
33
	----------
34
	doy: int
35
		Day of year, Jan 1 = 1
36
37
	Returns
38
	-------
39
	eot: float
40
		Equation of time correction in minutes
41
	"""
42 1
	D = doy - 1  # jan 1 = day zero
43 1
	W = 360.0 / 365.242
44 1
	A = W * (D + 10)
45 1
	B = A + 1.914 * np.sin(np.radians(W * (D - 2)))
46 1
	C = (np.radians(A) -
47
			np.arctan2(np.tan(np.radians(B)),
48
					np.cos(np.radians(23.44)))) / np.pi
49 1
	return 720.0 * (C - round(C))
50
51 1
class scia_limb_point(object):
52
	"""SCIAMACHY limb tangent point data
53
54
	Contains the data from a single tangent point."""
55 1
	def __init__(self, ls, i):
56
		self.date = []
57
		self.npix = 0
58
		self.orbit = 0
59
		self.sub_sat_lat = None
60
		self.sub_sat_lon = None
61
		self.tp_lat = None
62
		self.tp_lon = None
63
		self.tp_alt = None
64
		self.tp_sza = None
65
		self.tp_saa = None
66
		self.tp_los_zenith = None
67
		self.toa_sza = None
68
		self.toa_saa = None
69
		self.toa_los_zenith = None
70
		self.sat_sza = None
71
		self.sat_saa = None
72
		self.sat_los_zenith = None
73
		self.sat_alt = None
74
		self.earthradius = None
75
76
		self.wls = []
77
		self.rads = []
78
		self.errs = []
79
		self.limb_data = None
80
		self.from_limb_scan(ls, i)
81
82 1
	def from_limb_scan(self, ls, i):
83
		"""Import the spectra from a single tangent point of the limb scan
84
85
		Parameters
86
		----------
87
		ls : :class:`~sciapy.level1c.scia_limb_scan`
88
			The SCIAMACHY limb scan from which to extract the spectra.
89
		i : int
90
			The number of the tangent point in the limb scan
91
		"""
92
		self.date = ls.date
93
		self.npix = ls.npix
94
		self.orbit = ls.orbit
95
		if np.any(ls.limb_data.sub_sat_lat):
96
			self.sub_sat_lat = ls.limb_data.sub_sat_lat[i]
97
			self.sub_sat_lon = ls.limb_data.sub_sat_lon[i]
98
		self.tp_lat = ls.limb_data.tp_lat[i]
99
		self.tp_lon = ls.limb_data.tp_lon[i]
100
		self.tp_alt = ls.limb_data.tp_alt[i]
101
		self.tp_sza = ls.limb_data.tp_sza[i]
102
		self.tp_saa = ls.limb_data.tp_saa[i]
103
		self.tp_los_zenith = ls.limb_data.tp_los[i]
104
		self.toa_sza = ls.limb_data.toa_sza[i]
105
		self.toa_saa = ls.limb_data.toa_saa[i]
106
		self.toa_los_zenith = ls.limb_data.toa_los[i]
107
		self.sat_sza = ls.limb_data.sat_sza[i]
108
		self.sat_saa = ls.limb_data.sat_saa[i]
109
		self.sat_los_zenith = ls.limb_data.sat_los[i]
110
		self.sat_alt = ls.limb_data.sat_alt[i]
111
		self.earthradius = ls.limb_data.earth_rad[i]
112
113
		self.wls = ls.wls
114
		self.rads = ls.limb_data.rad[i]
115
		self.errs = ls.limb_data.err[i]
116
		self.limb_data = ls.limb_data[i]
117
118
119 1
class scia_limb_scan(object):
120
	"""SCIAMACHY limb scan data
121
122
	Contains the data from all or some selected tangent points.
123
	The format is inspired by the SCIAMACHY ascii data format.
124
125
	Attributes
126
	----------
127
	textheader_length : int
128
		The number of lines of the text header.
129
	textheader : str
130
		The header containing the limb scan meta data.
131
	metadata : dict
132
		Metadata of the limb scan as parsed by
133
		:func:`parse_textheader`. Contains:
134
135
		datatype_txt: str
136
			The name of the data type.
137
		l1b_product: str
138
			The level 1b product which was calibrated.
139
		orbit: int
140
			The Envisat/SCIAMACHY orbit number.
141
		state_id: int
142
			The SCIAMACHY state_id, denotes the measurement type
143
			that was carried out, i.e. nominal limb, MLT limb,
144
			nadir, sun or moon occultation etc.
145
		software_version: str
146
			The software used for calibration.
147
		keyfile_version: str
148
			The keyfile version used in the calibration process.
149
		mfactor_version: str
150
			The M-factor version used in the calibration process.
151
		init_version: str
152
			The init version used in the calibration process.
153
		decont_flags: str
154
			The decont flags used in the calibration process.
155
		calibration: str
156
			The calibrations that were applied to the level 1b data
157
			to produce the level 1c data.
158
		date: str
159
			The measurement data of the limb scan as "%Y%m%d"
160
		nr_profile: int
161
			The number of profiles in the scan.
162
		act_profile: int
163
			The number of the current profile.
164
	nalt : int
165
		The number of tangent points.
166
	npix : int
167
		The number of spectral points.
168
	orbit_state : tuple or list of ints
169
		Orbit state data containing
170
		(orbit number, state in orbit, state id,
171
		number of profiles per state (usually one),
172
		the actual profile number).
173
	date : tuple or list of ints
174
		The limb scan's date (year, month, day, hour, minute, second).
175
	cent_lat_lon : tuple or list of float
176
		The centre latitude and longitude of the scan followed by the
177
		four corner latitude and longitude:
178
		(lat_centre, lon_centre, lat_corner0, lon_corner0, ...,
179
		lat_corner3, lon_corner3).
180
	orbit_phase : float
181
		The orbital phase of the limb scan.
182
	wls: (N,) array_like
183
		The spectral wavelengths.
184
	limb_data : numpy.recarray
185
		The limb data containing the following records:
186
187
		sub_sat_lat: (M,) array_like
188
			The latitudes of the satellite ground points (M = nalt).
189
		sub_sat_lon: (M,) array_like
190
			The longitudes of the satellite ground points (M = nalt).
191
		tp_lat: (M,) array_like
192
			The latitudes of the tangent points (M = nalt).
193
		tp_lon: (M,) array_like
194
			The longitudes of the tangent points (M = nalt).
195
		tp_alt: (M,) array_like
196
			The tangent altitudes (M = nalt).
197
		tp_sza: (M,) array_like
198
			The solar zenith angles at the tangent points (M = nalt).
199
		tp_saa: (M,) array_like
200
			The solar azimuth angles at the tangent points (M = nalt).
201
		tp_los: (M,) array_like
202
			The line-of-sight zenith angles at the tangent points (M = nalt).
203
		toa_sza: (M,) array_like
204
			The solar zenith angles at the top-of-atmosphere points (M = nalt).
205
		toa_saa: (M,) array_like
206
			The solar azimuth angles at the top-of-atmosphere points (M = nalt).
207
		toa_los: (M,) array_like
208
			The line-of-sight zenith angles at the top-of-atmosphere points (M = nalt).
209
		sat_sza: (M,) array_like
210
			The solar zenith angles at the satellite points (M = nalt).
211
		sat_saa: (M,) array_like
212
			The solar azimuth angles at the satellite points (M = nalt).
213
		sat_los: (M,) array_like
214
			The line-of-sight zenith angles at the satellite points (M = nalt).
215
		sat_alt: (M,) array_like
216
			The satellite altitudes (M = nalt).
217
		earth_rad: (M,) array_like
218
			The earth radii at the tangent ground points (M = nalt).
219
		rad: (M, N) array_like
220
			The radiances at the tangent points, M = nalt, N = len(wls).
221
		err: (M, N) array_like
222
			The relative radiance uncertainties at the tangent points,
223
			M = nalt, N = len(wls).
224
	"""
225 1
	from .scia_limb_nc import read_from_netcdf, write_to_netcdf
226 1
	from .scia_limb_txt import read_from_textfile, write_to_textfile
227 1
	from .scia_limb_mpl import read_from_mpl_binary, write_to_mpl_binary
228 1
	from .scia_limb_hdf5 import (read_hdf5_limb_state_common_data,
229
								read_hdf5_limb_state_spectral_data,
230
								read_from_hdf5)
231
232 1
	def __init__(self):
233 1
		self._limb_data_dtype = None
234 1
		self.textheader_length = 0
235 1
		self.textheader = ""
236 1
		self.metadata = {}
237 1
		self.nalt = 0
238 1
		self.npix = 0
239 1
		self.orbit_state = []
240 1
		(self.orbit, self.state_in_orbit, self.state_id,
241
			self.profiles_per_state, self.profile_in_state) = (0, 0, 0, 0, 0)
242 1
		self.date = []
243 1
		self.cent_lat_lon = []
244 1
		self.orbit_phase = 0.
245
246 1
		self.limb_data = None
247
248 1
		self.wls = []
249
250 1
	def parse_textheader(self):
251
		"""Parses the ASCII header metadata
252
253
		The ASCII header text contains metadata about the current limb scan.
254
		This function reads this metadata into the :attr:`metadata` dictionary.
255
		"""
256 1
		from parse import parse
257 1
		split_header = self.textheader.split('\n')
258 1
		line = 0
259 1
		res = parse("#Data type          : {txt}", split_header[line])
260 1
		self.metadata["datatype_txt"] = res["txt"]
261 1
		line += 1
262 1
		res = parse("#L1b product        : {product}", split_header[line])
263 1
		self.metadata["l1b_product"] = res["product"]
264 1
		line += 1
265 1
		res = parse("#Orbit nr.,State ID : {orbit:05d} {state_id:2d}", split_header[line])
266 1
		self.metadata["orbit"] = res["orbit"]
267 1
		self.metadata["state_id"] = res["state_id"]
268 1
		line += 1
269 1
		res = parse("#Ver. Proc/Key/M/I/D: {soft}{:s}{key}  {mf}  {init}  {decont}",
270
				split_header[line])
271 1
		self.metadata["software_version"] = res["soft"]
272 1
		self.metadata["keyfile_version"] = res["key"]
273 1
		self.metadata["mfactor_version"] = res["mf"]
274 1
		self.metadata["init_version"] = res["init"]
275 1
		self.metadata["decont_flags"] = res["decont"]
276 1
		line += 1
277 1
		res = parse("#Calibr. appl. (0-8): {cal}", split_header[line])
278 1
		self.metadata["calibration"] = res["cal"]
279 1
		line += 1
280 1
		res = parse("#State Starttime    : {date}", split_header[line])
281 1
		self.metadata["date"] = res["date"]
282 1
		line += 1
283 1
		res = parse("#Nr Profiles / act. : {np:3d} {ap:3d}", split_header[line])
284 1
		self.metadata["nr_profile"] = res["np"]
285 1
		self.metadata["act_profile"] = res["ap"]
286
287 1
	def assemble_textheader(self):
288
		"""Combines the metadata to ASCII header
289
290
		Tranfers the :attr:`metadata` dictionary back to ASCII form
291
		for writing to disk.
292
		"""
293
		# Prepare the header
294
		meta = self.metadata
295
		if not meta:
296
			return
297
		n_header = 30
298
		line = n_header + 2
299
		header = ("#Data type          : {0[datatype_txt]}\n".format(meta))
300
		header += ("#L1b product        : {0[l1b_product]}\n".format(meta))
301
		header += ("#Orbit nr.,State ID : {0:05d} {1:2d}\n".format(meta["orbit"], meta["state_id"]))
302
		header += ("#Ver. Proc/Key/M/I/D: {0[software_version]:14s}  "
303
				"{0[keyfile_version]}  {0[mfactor_version]}  "
304
				"{0[init_version]}  {0[decont_flags]}\n"
305
				.format(meta))
306
		header += ("#Calibr. appl. (0-8): {0[calibration]}\n".format(meta))
307
		header += ("#State Starttime    : {0[date]}\n".format(meta))
308
		header += ("#Nr Profiles / act. : {0[nr_profile]:3d} {0[act_profile]:3d}\n".format(meta))
309
		header += ("# Angles TOA\n")
310
		header += ("#L.{0:2d} : Number_of_altitudes Number_of_pixels\n".format(line))
311
		line += 1
312
		header += ("#L.{0:2d} : Orbit State_in_orbit/file State-ID Profiles_per_state Profile_in_State\n".format(line))
313
		line += 1
314
		header += ("#L.{0:2d} : Date Time : yyyy mm dd hh mm ss\n".format(line))
315
		line += 1
316
317
		header += ("#L.{0:2d} : Sub satellite point lat\n".format(line))
318
		line += 1
319
		header += ("#L.{0:2d} : Sub satellite point lon\n".format(line))
320
		line += 1
321
		header += ("#L.{0:2d} : orbit phase [0..1]\n".format(line))
322
		line += 1
323
324
		header += ("#L.{0:2d} : Center(lat/lon) 4*Corners(lat/lon)\n".format(line))
325
		line += 1
326
		header += ("#L.{0:2d} : Tangent ground point lat\n".format(line))
327
		line += 1
328
		header += ("#L.{0:2d} : Tangent ground point lon\n".format(line))
329
		line += 1
330
		header += ("#L.{0:2d} : Tangent height\n".format(line))
331
		line += 1
332
333
		header += ("#L.{0:2d} : tangent pnt: Solar Zenith angle\n".format(line))
334
		line += 1
335
		header += ("#L.{0:2d} : tangent pnt: rel. Solar Azimuth angle\n".format(line))
336
		line += 1
337
		header += ("#L.{0:2d} : tangent pnt: LOS zenith\n".format(line))
338
		line += 1
339
		header += ("#L.{0:2d} : TOA: Solar Zenith angle\n".format(line))
340
		line += 1
341
		header += ("#L.{0:2d} : TOA: rel Solar Azimuth angle\n".format(line))
342
		line += 1
343
		header += ("#L.{0:2d} : TOA: LOS zenith\n".format(line))
344
		line += 1
345
		header += ("#L.{0:2d} : Sat: Solar Zenith angle\n".format(line))
346
		line += 1
347
		header += ("#L.{0:2d} : Sat: rel Solar Azimuth angle\n".format(line))
348
		line += 1
349
		header += ("#L.{0:2d} : Sat: LOS zenith\n".format(line))
350
		line += 1
351
352
		header += ("#L.{0:2d} : Sat. height\n".format(line))
353
		line += 1
354
		header += ("#L.{0:2d} : Earth radius\n".format(line))
355
		line += 1
356
		header += ("#L.{0:2d} : Npix lines : wavelength  n_altitude x radiance".format(line))
357
		self.textheader_length = n_header
358
		self.textheader = header
359
360 1
	def read_from_file(self, filename):
361
		"""SCIAMACHY level 1c limb scan general file import
362
363
		Tries `netcdf` first, the custom binary format second if
364
		netcdf fails, and finally ASCII import if that also fails.
365
		"""
366 1
		try:
367
			# try netcdf first
368 1
			self.read_from_netcdf(filename)
369 1
		except:
370 1
			try:
371
				# fall back to mpl binary
372 1
				self.read_from_mpl_binary(filename)
373
			except:
374
				# fall back to text file as a last resort
375
				self.read_from_textfile(filename)
376
377 1
	def local_solar_time(limb_scan, debug=True):
378
		"""Local solar time at limb scan footprint centre
379
380
		Returns
381
		-------
382
		(mean_lst, apparent_lst, eot_correction): tuple
383
			* mean_lst - mean local solar time
384
			* apparent_lst - apparent local solar time, equation of time corrected
385
			* eot_correction - equation of time correction in minutes
386
		"""
387 1
		import datetime as dt
388 1
		import logging
389 1
		dtime = dt.datetime(*limb_scan.date)
390 1
		doy = int(dtime.strftime("%j"))
391 1
		eot_correction = _equation_of_time(doy)
392 1
		hours, mins, secs = limb_scan.date[3:]
393 1
		clat, clon = limb_scan.cent_lat_lon[:2]
394 1
		if clon > 180.0:
395 1
			clon = clon - 360.0
396 1
		mean_lst = hours + mins / 60. + secs / 3600. + clon / 15.
397 1
		apparent_lst = mean_lst + eot_correction / 60.0
398
399 1
		if debug:
400
			logging.debug("%d %d %02d",
401
					limb_scan.orbit, limb_scan.state_in_orbit, doy)
402
			logging.debug("%s", limb_scan.orbit_state)
403
			logging.debug("%02d %02d %02d", hours, mins, secs)
404
			logging.debug("%.3f %.3f %.6f %.6f %.6f", clat, clon,
405
					mean_lst % 24, apparent_lst % 24, eot_correction)
406
		return mean_lst % 24, apparent_lst % 24, eot_correction
407