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

scia_limb_scan.local_solar_time()   A

Complexity

Conditions 3

Size

Total Lines 30
Code Lines 20

Duplication

Lines 30
Ratio 100 %

Code Coverage

Tests 14
CRAP Score 3.0987

Importance

Changes 0
Metric Value
cc 3
eloc 20
nop 2
dl 30
loc 30
ccs 14
cts 18
cp 0.7778
crap 3.0987
rs 9.4
c 0
b 0
f 0
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 View Code Duplication
def _equation_of_time(doy):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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 View Code Duplication
class scia_limb_point(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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 View Code Duplication
class scia_limb_scan(object):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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
	limb_data : numpy.recarray
183
		The limb data containing the following records:
184
185
		sub_sat_lat: (M,) array_like
186
			The latitudes of the satellite ground points (M = nalt).
187
		sub_sat_lon: (M,) array_like
188
			The longitudes of the satellite ground points (M = nalt).
189
		tp_lat: (M,) array_like
190
			The latitudes of the tangent points (M = nalt).
191
		tp_lon: (M,) array_like
192
			The longitudes of the tangent points (M = nalt).
193
		tp_alt: (M,) array_like
194
			The tangent altitudes (M = nalt).
195
		tp_sza: (M,) array_like
196
			The solar zenith angles at the tangent points (M = nalt).
197
		tp_saa: (M,) array_like
198
			The solar azimuth angles at the tangent points (M = nalt).
199
		tp_los: (M,) array_like
200
			The line-of-sight zenith angles at the tangent points (M = nalt).
201
		toa_sza: (M,) array_like
202
			The solar zenith angles at the top-of-atmosphere points (M = nalt).
203
		toa_saa: (M,) array_like
204
			The solar azimuth angles at the top-of-atmosphere points (M = nalt).
205
		toa_los: (M,) array_like
206
			The line-of-sight zenith angles at the top-of-atmosphere points (M = nalt).
207
		sat_sza: (M,) array_like
208
			The solar zenith angles at the satellite points (M = nalt).
209
		sat_saa: (M,) array_like
210
			The solar azimuth angles at the satellite points (M = nalt).
211
		sat_los: (M,) array_like
212
			The line-of-sight zenith angles at the satellite points (M = nalt).
213
		sat_alt: (M,) array_like
214
			The satellite altitudes (M = nalt).
215
		earth_rad: (M,) array_like
216
			The earth radii at the tangent ground points (M = nalt).
217
		wls: (N,) array_like
218
			The spectral wavelengths.
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 1
		self.meta_data = {}
248
249 1
		self.wls = []
250
251 1
	def parse_textheader(self):
252
		"""Parses the ASCII header metadata
253
254
		The ASCII header text contains metadata about the current limb scan.
255
		This function reads this metadata into the :attr:`metadata` dictionary.
256
		"""
257 1
		from parse import parse
258 1
		split_header = self.textheader.split('\n')
259 1
		line = 0
260 1
		res = parse("#Data type          : {txt}", split_header[line])
261 1
		self.metadata["datatype_txt"] = res["txt"]
262 1
		line += 1
263 1
		res = parse("#L1b product        : {product}", split_header[line])
264 1
		self.metadata["l1b_product"] = res["product"]
265 1
		line += 1
266 1
		res = parse("#Orbit nr.,State ID : {orbit:05d} {state_id:2d}", split_header[line])
267 1
		self.metadata["orbit"] = res["orbit"]
268 1
		self.metadata["state_id"] = res["state_id"]
269 1
		line += 1
270 1
		res = parse("#Ver. Proc/Key/M/I/D: {soft}{:s}{key}  {mf}  {init}  {decont}",
271
				split_header[line])
272 1
		self.metadata["software_version"] = res["soft"]
273 1
		self.metadata["keyfile_version"] = res["key"]
274 1
		self.metadata["mfactor_version"] = res["mf"]
275 1
		self.metadata["init_version"] = res["init"]
276 1
		self.metadata["decont_flags"] = res["decont"]
277 1
		line += 1
278 1
		res = parse("#Calibr. appl. (0-8): {cal}", split_header[line])
279 1
		self.metadata["calibration"] = res["cal"]
280 1
		line += 1
281 1
		res = parse("#State Starttime    : {date}", split_header[line])
282 1
		self.metadata["date"] = res["date"]
283 1
		line += 1
284 1
		res = parse("#Nr Profiles / act. : {np:3d} {ap:3d}", split_header[line])
285 1
		self.metadata["nr_profile"] = res["np"]
286 1
		self.metadata["act_profile"] = res["ap"]
287
288 1
	def assemble_textheader(self):
289
		"""Combines the metadata to ASCII header
290
291
		Tranfers the :attr:`metadata` dictionary back to ASCII form
292
		for writing to disk.
293
		"""
294
		# Prepare the header
295
		meta = self.metadata
296
		if not meta:
297
			return
298
		n_header = 30
299
		line = n_header + 2
300
		header = ("#Data type          : {0[datatype_txt]}\n".format(meta))
301
		header += ("#L1b product        : {0[l1b_product]}\n".format(meta))
302
		header += ("#Orbit nr.,State ID : {0:05d} {1:2d}\n".format(meta["orbit"], meta["state_id"]))
303
		header += ("#Ver. Proc/Key/M/I/D: {0[software_version]:14s}  "
304
				"{0[keyfile_version]}  {0[mfactor_version]}  "
305
				"{0[init_version]}  {0[decont_flags]}\n"
306
				.format(meta))
307
		header += ("#Calibr. appl. (0-8): {0[calibration]}\n".format(meta))
308
		header += ("#State Starttime    : {0[date]}\n".format(meta))
309
		header += ("#Nr Profiles / act. : {0[nr_profile]:3d} {0[act_profile]:3d}\n".format(meta))
310
		header += ("# Angles TOA\n")
311
		header += ("#L.{0:2d} : Number_of_altitudes Number_of_pixels\n".format(line))
312
		line += 1
313
		header += ("#L.{0:2d} : Orbit State_in_orbit/file State-ID Profiles_per_state Profile_in_State\n".format(line))
314
		line += 1
315
		header += ("#L.{0:2d} : Date Time : yyyy mm dd hh mm ss\n".format(line))
316
		line += 1
317
318
		header += ("#L.{0:2d} : Sub satellite point lat\n".format(line))
319
		line += 1
320
		header += ("#L.{0:2d} : Sub satellite point lon\n".format(line))
321
		line += 1
322
		header += ("#L.{0:2d} : orbit phase [0..1]\n".format(line))
323
		line += 1
324
325
		header += ("#L.{0:2d} : Center(lat/lon) 4*Corners(lat/lon)\n".format(line))
326
		line += 1
327
		header += ("#L.{0:2d} : Tangent ground point lat\n".format(line))
328
		line += 1
329
		header += ("#L.{0:2d} : Tangent ground point lon\n".format(line))
330
		line += 1
331
		header += ("#L.{0:2d} : Tangent height\n".format(line))
332
		line += 1
333
334
		header += ("#L.{0:2d} : tangent pnt: Solar Zenith angle\n".format(line))
335
		line += 1
336
		header += ("#L.{0:2d} : tangent pnt: rel. Solar Azimuth angle\n".format(line))
337
		line += 1
338
		header += ("#L.{0:2d} : tangent pnt: LOS zenith\n".format(line))
339
		line += 1
340
		header += ("#L.{0:2d} : TOA: Solar Zenith angle\n".format(line))
341
		line += 1
342
		header += ("#L.{0:2d} : TOA: rel Solar Azimuth angle\n".format(line))
343
		line += 1
344
		header += ("#L.{0:2d} : TOA: LOS zenith\n".format(line))
345
		line += 1
346
		header += ("#L.{0:2d} : Sat: Solar Zenith angle\n".format(line))
347
		line += 1
348
		header += ("#L.{0:2d} : Sat: rel Solar Azimuth angle\n".format(line))
349
		line += 1
350
		header += ("#L.{0:2d} : Sat: LOS zenith\n".format(line))
351
		line += 1
352
353
		header += ("#L.{0:2d} : Sat. height\n".format(line))
354
		line += 1
355
		header += ("#L.{0:2d} : Earth radius\n".format(line))
356
		line += 1
357
		header += ("#L.{0:2d} : Npix lines : wavelength  n_altitude x radiance".format(line))
358
		self.textheader_length = n_header
359
		self.textheader = header
360
361 1
	def read_from_file(self, filename):
362
		"""SCIAMACHY level 1c limb scan general file import
363
364
		Tries `netcdf` first, the custom binary format second if
365
		netcdf fails, and finally ASCII import if that also fails.
366
		"""
367 1
		try:
368
			# try netcdf first
369 1
			self.read_from_netcdf(filename)
370 1
		except:
371 1
			try:
372
				# fall back to mpl binary
373 1
				self.read_from_mpl_binary(filename)
374
			except:
375
				# fall back to text file as a last resort
376
				self.read_from_textfile(filename)
377
378 1
	def local_solar_time(limb_scan, debug=True):
379
		"""Local solar time at limb scan footprint centre
380
381
		Returns
382
		-------
383
		(mean_lst, apparent_lst, eot_correction): tuple
384
			* mean_lst - mean local solar time
385
			* apparent_lst - apparent local solar time, equation of time corrected
386
			* eot_correction - equation of time correction in minutes
387
		"""
388 1
		import datetime as dt
389 1
		import logging
390 1
		dtime = dt.datetime(*limb_scan.date)
391 1
		doy = int(dtime.strftime("%j"))
392 1
		eot_correction = _equation_of_time(doy)
393 1
		hours, mins, secs = limb_scan.date[3:]
394 1
		clat, clon = limb_scan.cent_lat_lon[:2]
395 1
		if clon > 180.0:
396 1
			clon = clon - 360.0
397 1
		mean_lst = hours + mins / 60. + secs / 3600. + clon / 15.
398 1
		apparent_lst = mean_lst + eot_correction / 60.0
399
400 1
		if debug:
401
			logging.debug("%d %d %02d",
402
					limb_scan.orbit, limb_scan.state_in_orbit, doy)
403
			logging.debug("%s", limb_scan.orbit_state)
404
			logging.debug("%02d %02d %02d", hours, mins, secs)
405
			logging.debug("%.3f %.3f %.6f %.6f %.6f", clat, clon,
406
					mean_lst % 24, apparent_lst % 24, eot_correction)
407
		return mean_lst % 24, apparent_lst % 24, eot_correction
408