sciapy.level1c.scia_limb_hdf5   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 434
Duplicated Lines 0 %

Test Coverage

Coverage 6.16%

Importance

Changes 0
Metric Value
eloc 256
dl 0
loc 434
ccs 13
cts 211
cp 0.0616
rs 10
c 0
b 0
f 0
wmc 16

5 Functions

Rating   Name   Duplication   Size   Complexity  
A _middle_coord() 0 7 1
A read_from_hdf5() 0 47 4
C read_hdf5_limb_state_common_data() 0 204 6
A read_hdf5_limb_state_spectral_data() 0 48 4
B _calc_angles() 0 85 1
1
# -*- coding: utf-8 -*-
2
# vim:fileencoding=utf-8
3
#
4
# Copyright (c) 2017 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 1c limb spectra hdf5 interface
12
"""
13
14 1
from __future__ import absolute_import, division, print_function
15
16 1
import logging
17
18 1
import numpy as np
19 1
from astropy.time import Time
20
21 1
from ._types import _limb_data_dtype
22
23 1
logging.basicConfig(level=logging.INFO,
24
		format="[%(levelname)-8s] (%(asctime)s) "
25
		"%(filename)s:%(lineno)d %(message)s",
26
		datefmt="%Y-%m-%d %H:%M:%S %z")
27
28 1
def _calc_angles(sza_in, los_in, saa_in, z_in, z_out, earth_radius):
29
	"""SCIAMACHY limb scan angle setup
30
31
	Calculates the solar zenith, solar azimuth, and line-of-sight angles
32
	to the tangent point (tp), to the top of the atmosphere (toa),
33
	and to the satellite (sat). All angles are in degrees.
34
35
	Parameters
36
	----------
37
	sza_in : ndarray
38
		The solar zenith angles (per tangent point).
39
	los_in : ndarray
40
		The line-of-sight zenith angles (per tangent point).
41
	saa_in : ndarray
42
		The relative solar azimuth angles (per tangent point).
43
	z_in : ndarray
44
		The input altitudes, can be tangent heights, top of
45
		atmosphere, or satellite altitudes.
46
	z_out : ndarray
47
		The output altitude, can be tangent heights, top of
48
		atmosphere, or satellite altitudes.
49
	earth_radius : ndarray
50
		The earth radii (per tangent point).
51
52
	Returns
53
	-------
54
	sza_out : ndarray
55
		The calculated solar zenith angles for the output altitudes.
56
	los_out : ndarray
57
		The calculated line-of-sight angles for the output altitudes.
58
	saa_out : ndarray
59
		The calculated solar azimuth angles for the output altitudes.
60
	"""
61
	def sign(x, y):
62
		xa = np.full_like(y, np.abs(x))
63
		xa[np.where(y < 0.)] *= -1
64
		return xa
65
66
	cos_psi_in = np.cos(np.radians(sza_in))
67
	mju_in = np.cos(np.radians(los_in))
68
	cos_phi_0 = np.cos(np.radians(saa_in))
69
	sin_phi_0 = np.sin(np.radians(saa_in))
70
71
	# /* start original calculation in angles.f */
72
	r = earth_radius + z_in
73
	sin_1 = np.sqrt(1.0 - mju_in**2)
74
	h_0 = r * (sin_1 - 1.0) + z_in
75
76
	z_out[np.where(z_out < h_0)] = h_0[np.where(z_out < h_0)]
77
78
	delta = (np.sqrt((2.0 * earth_radius + z_in + h_0) * (z_in - h_0)) -
79
			np.sqrt((2.0 * earth_radius + z_out + h_0) * (z_out - h_0)))
80
	mju_out = ((mju_in * r - delta) /
81
			np.sqrt((mju_in * r - delta)**2 + (r * sin_1)**2))
82
	sin_out = r * sin_1 / (earth_radius + z_out)
83
	sin_psi = np.sqrt(1.0 - cos_psi_in**2)
84
	zeta_0 = mju_in * cos_psi_in - sin_1 * sin_psi * cos_phi_0
85
	ksi_0 = mju_in * sin_psi + sin_1 * cos_psi_in * cos_phi_0
86
87
	cos_psi_out = ((cos_psi_in * r - delta * zeta_0) /
88
			np.sqrt((r * cos_psi_in - delta * zeta_0)**2 +
89
				(r * sin_psi - delta * ksi_0)**2 +
90
				(delta * sin_1 * sin_phi_0)**2))
91
	sin_psi_out = np.sqrt(1.0 - np.clip(cos_psi_out * cos_psi_out, 1.0, np.inf))
92
	#sin_psi_out = np.sqrt(1.0 - np.clip(cos_psi_out*cos_psi_out, -np.inf, 1.0));
93
94
	eta_0 = ((r - delta * mju_in) * sin_psi * cos_phi_0 - delta * sin_1 * cos_psi_in) / (earth_radius + z_out)
95
96
	eta_0 = eta_0 + 1.0e-39  # /*! numerical stabilization*/
97
98
	s1 = eta_0 / np.sqrt(eta_0**2 + (sin_psi * sin_phi_0)**2)
99
	s1 = s1 - sign(1.0e-13, s1)  # /* ! numerical stabilization*/
100
101
	sd = r * sin_psi * sin_1 * sin_phi_0 / (earth_radius + z_out) / (sin_psi_out * sin_out + 1.0e-78)
102
103
	phi_out = sign(np.arccos(s1), sd)
104
105
	sza_out = np.degrees(np.arccos(cos_psi_out))
106
	los_out = np.degrees(np.arccos(mju_out))
107
	saa_out = np.degrees(phi_out)
108
	# /* set back direction of line of sight
109
	# if (z_out > z_in)
110
	# *saa_out -= PI; */
111
	logging.debug("calculated tangent_height: %s", h_0)
112
	return (sza_out, los_out, saa_out)
113
114 1
def _middle_coord(lat1, lon1, lat2, lon2):
115
	sin_lat = 0.5 * (np.sin(np.radians(lat1)) + np.sin(np.radians(lat2)))
116
	cos_lat = 0.5 * (np.cos(np.radians(lat1)) + np.cos(np.radians(lat2)))
117
	sin_lon = 0.5 * (np.sin(np.radians(lon1)) + np.sin(np.radians(lon2)))
118
	cos_lon = 0.5 * (np.cos(np.radians(lon1)) + np.cos(np.radians(lon2)))
119
	return (np.degrees(np.arctan2(sin_lat, cos_lat)),
120
			np.degrees(np.arctan2(sin_lon, cos_lon)))
121
122 1
_state_txt = {
123
	27: "SCIAMACHY limb eclips",
124
	28: "SCIAMACHY limb",
125
	29: "SCIAMACHY limb",
126
	30: "SCIAMACHY limb",
127
	31: "SCIAMACHY limb",
128
	32: "SCIAMACHY limb",
129
	33: "SCIAMACHY limb",
130
	55: "SCIAMACHY limb mesosp",
131
}
132
133 1
def read_hdf5_limb_state_common_data(self, hf, lstate_id, state_in_orbit, cl_id):
134
	"""SCIAMACHY level 1c common data
135
136
	Parameters
137
	----------
138
	hf : opened file
139
		Pointer to the opened level 1c HDF5 file
140
	lstate_id : int
141
		The limb state id.
142
	state_in_orbit : int
143
		The number in this batch of states for the header.
144
	cl_id : int
145
		The spectral cluster number.
146
147
	Returns
148
	-------
149
	success : int
150
		0 on success,
151
		1 if an error occurred, for example if the measurement data
152
		set for the requested limb and cluster ids is empty.
153
	"""
154
	# MDS = measurement data set
155
	cl_mds_group_name = "/MDS/limb_{0:02d}/cluster_{1:02d}".format(lstate_id, cl_id + 1)
156
	cl_mds_group = hf.get(cl_mds_group_name)
157
	if cl_mds_group is None:
158
		return 1
159
	# Load meta data
160
	self.metadata["calibration"] = hf.attrs["Calibration"].decode()
161
	self.metadata["l1b_product"] = hf.get("/MPH")["product_name"][0].decode()
162
	self.metadata["orbit"] = hf.get("/MPH")["abs_orbit"][0]
163
	self.metadata["state_id"] = hf.get("/ADS/STATES")["state_id"][lstate_id]
164
	self.metadata["software_version"] = hf.get("/MPH")["software_version"][0].decode()
165
	self.metadata["keyfile_version"] = hf.get("/SPH")["key_data_version"][0].decode()
166
	self.metadata["mfactor_version"] = hf.get("/SPH")["m_factor_version"][0].decode()
167
	init_ver = hf.get("/SPH")["init_version"][0].decode().strip()
168
	self.metadata["init_version"], decont = init_ver.split()
169
	self.metadata["decont_flags"] = decont.lstrip("DECONT=")
170
	orb_phase = hf.get("/ADS/STATES")["orb_phase"][lstate_id]
171
	j_day_0 = 2451544.5  # 2000-01-01
172
	dsr_d, dsr_s, dsr_us = hf.get("/ADS/STATES")["dsr_time"][lstate_id]
173
	state_dt = Time(dsr_d + j_day_0 + dsr_s / 86400. + dsr_us / (86400. * 1e6),
174
			format="jd").datetime
175
	self.metadata["date"] = state_dt.strftime("%d-%b-%Y %H:%M:%S.%f")
176
177
	logging.debug("applied calibrations: %s", self.metadata["calibration"])
178
	logging.debug("product: %s, orbit_nr: %s, state_id: %s, orb_phase: %s",
179
			self.metadata["l1b_product"], self.metadata["orbit"],
180
			self.metadata["state_id"], orb_phase)
181
	logging.debug("soft_ver: %s, key_ver: %s, mf_ver: %s, init_ver: %s, "
182
			"decont_ver: %s", self.metadata["software_version"],
183
			self.metadata["keyfile_version"], self.metadata["mfactor_version"],
184
			self.metadata["init_version"], self.metadata["decont_flags"])
185
186
	ads_state = hf.get("/ADS/STATES")[lstate_id]
187
	cl_n_readouts = ads_state["clus_config"]["num_readouts"][cl_id]
188
	cl_intg_time = ads_state["clus_config"]["intg_time"][cl_id]
189
	self.metadata["nr_profile"] = 24 // (cl_intg_time * cl_n_readouts)
190
	self.metadata["act_profile"] = 0  # always zero for now
191
192
	# Prepare the header
193
	try:
194
		self.metadata["datatype_txt"] = _state_txt[self.metadata["state_id"]]
195
	except KeyError:
196
		logging.warn("State id %s not supported.", self.metadata["state_id"])
197
		return 1
198
	self.assemble_textheader()
199
	logging.debug("header:\n%s", self.textheader)
200
201
	# parse geolocation data
202
	gr_scia_geo = cl_mds_group.get("geoL_scia")
203
	tan_h = gr_scia_geo["tan_h"]
204
	# lat and lon are integers in degrees * 10^6
205
	lats_all = gr_scia_geo["tang_ground_point"]["lat"]
206
	lons_all = gr_scia_geo["tang_ground_point"]["lon"]
207
	sza_all = gr_scia_geo["sun_zen_ang"]
208
	saa_all = (gr_scia_geo["sun_azi_ang"] - gr_scia_geo["los_azi_ang"])
209
	sat_h_all = gr_scia_geo["sat_h"]
210
	earth_rad_all = gr_scia_geo["earth_rad"]
211
	# lat and lon are integers in degrees * 10^6
212
	subsatlat_all = gr_scia_geo["sub_sat_point"]["lat"]
213
	subsatlon_all = gr_scia_geo["sub_sat_point"]["lon"]
214
	# fix longitudes to [0°, 360°)
215
	lons_all[np.where(lons_all < 0)] += 360000000
216
217
	if cl_n_readouts > 2:
218
		tangent_heights = 0.5 * (tan_h[1::cl_n_readouts, 2] + tan_h[2::cl_n_readouts, 0])
219
		tp_lats = 0.5 * (lats_all[1::cl_n_readouts, 2] + lats_all[2::cl_n_readouts, 0]) * 1e-6
220
		tp_lons = 0.5 * (lons_all[1::cl_n_readouts, 2] + lons_all[2::cl_n_readouts, 0]) * 1e-6
221
		sza_toa = 0.5 * (sza_all[1::cl_n_readouts, 2] + sza_all[2::cl_n_readouts, 0])
222
		saa_toa = 0.5 * (saa_all[1::cl_n_readouts, 2] + saa_all[2::cl_n_readouts, 0])
223
		sat_hs = sat_h_all.reshape((-1, cl_n_readouts)).mean(axis=1)
224
		earth_rads = earth_rad_all.reshape((-1, cl_n_readouts)).mean(axis=1)
225
		subsatlat = subsatlat_all.reshape((-1, cl_n_readouts)).mean(axis=1) * 1e-6
226
		subsatlon = subsatlon_all.reshape((-1, cl_n_readouts)).mean(axis=1) * 1e-6
227
	else:
228
		tangent_heights = tan_h[::cl_n_readouts, 1]
229
		tp_lats = lats_all[::cl_n_readouts, 1] * 1e-6
230
		tp_lons = lons_all[::cl_n_readouts, 1] * 1e-6
231
		sza_toa = sza_all[::cl_n_readouts, 1]
232
		saa_toa = saa_all[::cl_n_readouts, 1]
233
		sat_hs = sat_h_all[::cl_n_readouts]
234
		earth_rads = earth_rad_all[::cl_n_readouts]
235
		subsatlat = subsatlat_all[::cl_n_readouts] * 1e-6
236
		subsatlon = subsatlon_all[::cl_n_readouts] * 1e-6
237
238
	logging.debug("tangent altitudes: %s", tangent_heights)
239
	nalt = len(tangent_heights)
240
241
	centre = _middle_coord(lats_all[0, 1] * 1e-6, lons_all[0, 1] * 1e-6,
242
			lats_all[nalt - 2, 1] * 1e-6, lons_all[nalt - 2, 1] * 1e-6)
243
244
	cent_lat_lon = (centre[0],
245
			# fix longitudes to [0, 360.)
246
			centre[1] if centre[1] >= 0. else 360. + centre[1],
247
			lats_all[0, 0] * 1e-6, lons_all[0, 0] * 1e-6,
248
			lats_all[0, 2] * 1e-6, lons_all[0, 2] * 1e-6,
249
			lats_all[nalt - 2, 0] * 1e-6, lons_all[nalt - 2, 0] * 1e-6,
250
			lats_all[nalt - 2, 2] * 1e-6, lons_all[nalt - 2, 2] * 1e-6)
251
252
	toa = 100.
253
	# to satellite first
254
	los_calc = np.degrees(np.arccos(0.0))
255
	sza_tp_h = sza_toa.copy()
256
	saa_tp_h = saa_toa.copy()
257
	los_tp_h = np.full_like(tangent_heights, los_calc)
258
	los_toa_h = np.full_like(tangent_heights, los_calc)
259
	sza_sat_h, los_sat_h, saa_sat_h = _calc_angles(
260
			sza_toa, los_calc, saa_toa,
261
			tangent_heights, sat_hs, earth_rads)
262
	# angles toa
263
	los_calc = np.degrees(np.arcsin((tangent_heights + earth_rads) / (toa + earth_rads)))
264
	# to tangent point
265
	los_toa_l = np.full_like(tangent_heights, los_calc)
266
	sza_tp_l, los_tp_l, saa_tp_l = _calc_angles(
267
			sza_toa, los_calc, saa_toa,
268
			np.full_like(tangent_heights, toa), tangent_heights,
269
			earth_rads)
270
	# to satellite
271
	sza_sat_l, los_sat_l, saa_sat_l = _calc_angles(
272
			sza_toa, los_calc, saa_toa,
273
			np.full_like(tangent_heights, toa), sat_hs,
274
			earth_rads)
275
276
	sza_sat_h[np.where(tangent_heights <= toa)] = 0.
277
	sza_sat_l[np.where(tangent_heights > toa)] = 0.
278
	saa_sat_h[np.where(tangent_heights <= toa)] = 0.
279
	saa_sat_l[np.where(tangent_heights > toa)] = 0.
280
	los_sat_h[np.where(tangent_heights <= toa)] = 0.
281
	los_sat_l[np.where(tangent_heights > toa)] = 0.
282
283
	sza_tp_h[np.where(tangent_heights <= toa)] = 0.
284
	sza_tp_l[np.where(tangent_heights > toa)] = 0.
285
	saa_tp_h[np.where(tangent_heights <= toa)] = 0.
286
	saa_tp_l[np.where(tangent_heights > toa)] = 0.
287
	los_tp_h[np.where(tangent_heights <= toa)] = 0.
288
	los_tp_l[np.where(tangent_heights > toa)] = 0.
289
290
	los_toa_h[np.where(tangent_heights <= toa)] = 0.
291
	los_toa_l[np.where(tangent_heights > toa)] = 0.
292
293
	sza_sat = sza_sat_h + sza_sat_l
294
	saa_sat = saa_sat_h + saa_sat_l
295
	los_sat = los_sat_h + los_sat_l
296
297
	sza_tp = sza_tp_h + sza_tp_l
298
	saa_tp = saa_tp_h + saa_tp_l
299
	los_tp = los_tp_h + los_tp_l
300
301
	los_toa = los_toa_h + los_toa_l
302
303
	logging.debug("TP sza, saa, los: %s, %s, %s", sza_tp, saa_tp, los_tp)
304
	logging.debug("TOA sza, saa, los: %s, %s, %s", sza_toa, saa_toa, los_toa)
305
	logging.debug("SAT sza, saa, los: %s, %s, %s", sza_sat, saa_sat, los_sat)
306
307
	# save the data to the limb scan class
308
	self.nalt = nalt
309
	self.orbit_state = (self.metadata["orbit"], state_in_orbit,
310
			self.metadata["state_id"],
311
			self.metadata["nr_profile"], self.metadata["act_profile"])
312
	self.date = (state_dt.year, state_dt.month, state_dt.day,
313
			state_dt.hour, state_dt.minute, state_dt.second)
314
	self.orbit_phase = orb_phase
315
	self.cent_lat_lon = cent_lat_lon
316
	# pre-set the limb_data
317
	if self._limb_data_dtype is None:
318
		self._limb_data_dtype = _limb_data_dtype[:]
319
	self.limb_data = np.zeros((self.nalt), dtype=self._limb_data_dtype)
320
	self.limb_data["sub_sat_lat"] = subsatlat
321
	self.limb_data["sub_sat_lon"] = subsatlon
322
	self.limb_data["tp_lat"] = tp_lats
323
	self.limb_data["tp_lon"] = tp_lons
324
	self.limb_data["tp_alt"] = tangent_heights
325
	self.limb_data["tp_sza"] = sza_tp
326
	self.limb_data["tp_saa"] = saa_tp
327
	self.limb_data["tp_los"] = los_tp
328
	self.limb_data["toa_sza"] = sza_toa
329
	self.limb_data["toa_saa"] = saa_toa
330
	self.limb_data["toa_los"] = los_toa
331
	self.limb_data["sat_sza"] = sza_sat
332
	self.limb_data["sat_saa"] = saa_sat
333
	self.limb_data["sat_los"] = los_sat
334
	self.limb_data["sat_alt"] = sat_hs
335
	self.limb_data["earth_rad"] = earth_rads
336
	return 0
337
338 1
def read_hdf5_limb_state_spectral_data(self, hf, lstate_id, cl_id):
339
	"""SCIAMACHY level 1c spectral data
340
341
	Parameters
342
	----------
343
	hf : opened file
344
		Pointer to the opened level 1c HDF5 file
345
	lstate_id : int
346
		The limb state id.
347
	cl_id : int
348
		The spectral cluster number.
349
350
	Returns
351
	-------
352
	success : int
353
		0 on success,
354
		1 if an error occurred, for example if the measurement data
355
		set for the requested limb and cluster ids is empty.
356
	"""
357
	cl_mds_group_name = "/MDS/limb_{0:02d}/cluster_{1:02d}".format(lstate_id, cl_id + 1)
358
	cl_mds_group = hf.get(cl_mds_group_name)
359
	if cl_mds_group is None:
360
		return 1
361
362
	ads_state = hf.get("/ADS/STATES")[lstate_id]
363
	cl_n_readouts = ads_state["clus_config"]["num_readouts"][cl_id]
364
365
	pwl = cl_mds_group.get("pixel_wavelength")[:]
366
	signal = cl_mds_group.get("pixel_signal")[:]
367
	sig_errs = cl_mds_group.get("pixel_signal_error")[:]
368
	if cl_n_readouts > 1:
369
		# coadd data
370
		signal_coadd = signal.reshape((-1, cl_n_readouts, len(pwl))).sum(axis=1)
371
		sig_errs = np.sqrt(((sig_errs * signal)**2)
372
					.reshape((-1, cl_n_readouts, len(pwl)))
373
					.sum(axis=1)) / np.abs(signal_coadd)
374
		signal = signal_coadd / cl_n_readouts
375
	if np.any(self.wls):
376
		# apparently we already have some data, so concatenate
377
		self.wls = np.concatenate([self.wls, pwl], axis=0)
378
		self._rad_arr = np.concatenate([self._rad_arr, signal], axis=1)
379
		self._err_arr = np.concatenate([self._err_arr, sig_errs], axis=1)
380
	else:
381
		# this seems to be the first time we fill the arrays
382
		self.wls = pwl
383
		self._rad_arr = signal
384
		self._err_arr = sig_errs
385
	return 0
386
387 1
def read_from_hdf5(self, hf, limb_state_id, state_in_orbit, cluster_ids):
388
	"""SCIAMACHY level 1c HDF main interface
389
390
	This should be the function to be used for HDF5 reading.
391
	It reads the common data and iterates over the spectral
392
	clusters to fill the class data.
393
394
	Parameters
395
	----------
396
	hf : opened file
397
		Pointer to the opened level 1c HDF5 file
398
	limb_state_id : int
399
		The limb state id.
400
	state_in_orbit : int
401
		The number in this batch of states for the header.
402
	cluster_ids : int or sequence of ints
403
		The spectral cluster numbers.
404
405
	Returns
406
	-------
407
	success : int
408
		0 on success,
409
		1 if an error occurred, for example if the measurement data
410
		set for the requested limb and cluster ids is empty.
411
	"""
412
	import numpy.lib.recfunctions as rfn
413
	if not hasattr(cluster_ids, '__getitem__'):
414
		cluster_ids = [cluster_ids]
415
416
	if self.read_hdf5_limb_state_common_data(hf, limb_state_id,
417
			state_in_orbit, cluster_ids[0]):
418
		return 1
419
420
	for cl_id in cluster_ids:
421
		self.read_hdf5_limb_state_spectral_data(hf, limb_state_id, cl_id)
422
423
	self.npix = len(self.wls)
424
425
	rads = np.rec.fromarrays([self._rad_arr],
426
				dtype=np.dtype([("rad", 'f4', (self.npix,))]))
427
	errs = np.rec.fromarrays([self._err_arr],
428
				dtype=np.dtype([("err", 'f4', (self.npix,))]))
429
	self.limb_data = rfn.merge_arrays([self.limb_data, rads, errs],
430
			usemask=False, asrecarray=True, flatten=True)
431
	self._limb_data_dtype = self.limb_data.dtype
432
433
	return 0
434