OB.DAAC Logo
NASA Logo
Ocean Color Science Software

ocssw V2022
get_bpar.c
Go to the documentation of this file.
1 /* =================================================================== */
2 /* module get_bpar.c - compute PAR at depth */
3 /* */
4 /* Experimental code to estimate photosynthetically active radiation */
5 /* (PAR) at the seafloor or other user-defined depth. Code assumes */
6 /* clear sky conditions only. */
7 /* */
8 /* References: */
9 /* Magno-Canto et al. 2019. Model for deriving benthic irradiance in */
10 /* the Great Barrier Reef from MODIS satellite imagery, Optics Express,*/
11 /* 27(20), A1350-A1371. */
12 /* */
13 /* Implementation: */
14 /* L. McKinna Go2Q, Jan 2020 */
15 /* M. Canto JCU/AIMS, Jan 2020 */
16 /* */
17 /* User inputs: */
18 /* bpar_validate_opt (default = 0) */
19 /* 0: compute using solar zenith at local solar noon */
20 /* 1: compute using solar zentih at overpass time */
21 /* */
22 /* bpar_elev_opt (default=0) */
23 /* 0: use bathymetry data */
24 /* 1: use scene-wide user-defined depth value */
25 /* */
26 /* bpar_elev_value (default=30.0) {only used when bpar_elev_opt=1} */
27 /* float: input positive value for scene-wide depth value */
28 /* */
29 /* Notes: */
30 /* Daily integrated values do not consider variability in atmospheric */
31 /* conditions and assume a cloud free sky. */
32 /* =================================================================== */
33 
34 #include <stdio.h>
35 #include <math.h>
36 #include "l12_proto.h"
37 
38 #define KD_MAX 6.4
39 #define KD_MIN 0.016
40 
41 static float badval = BAD_FLT;
42 static int32_t LastRecNum = -1;
43 int32_t timeStepNum = 50;
44 
45 static float *iparb; //PAR at seafloor
46 static float *parb; //Daily PAR at seafloor
47 static double *ed; //above-water down-welling irradiance
48 static double *edt; //above-water down-welling irradiance
49 static double *edsubq; //sub-surface down-welling irradiance
50 static double *kdtot; //diffuse attenuation coefficient
51 static double *edzq; //quantum irradiance at seafloor in micromol/m2/s
52 static double *solzArr; //Array of solar zenith angles from sunrise to midday
53 static double *solTimeArr;
54 static double *partemp;
55 
56 static int32_t nbandVis; //number of visible bands for a given sensor
57 static double solStepSec; //integral time step in seconds
58 
59 /* ------------------------------------------------------------------------*/
60 /* bpar_ran() - determin in bpar has been run for scanline */
61 /* ------------------------------------------------------------------------*/
62 int bpar_ran(int recnum)
63 {
64  if ( recnum == LastRecNum )
65  return 1;
66  else
67  return 0;
68 }
69 
70 /* ---------------------------------------------------------------------- */
71 /* bpar_init() - allocates memory for benthic par algorithm */
72 /* ---------------------------------------------------------------------- */
73 void bpar_init(l2str *l2rec) {
74 
75  l1str *l1rec = l2rec->l1rec;
76  int32_t nbands = l1rec->l1file->nbands;
77 
78  ed = (double*) allocateMemory(l1rec->npix *nbands * sizeof(double), "ed");
79  edsubq = (double*) allocateMemory(l1rec->npix *nbands * sizeof(double), "edsubq");
80  kdtot = (double*) allocateMemory(l1rec->npix *nbands * sizeof(double), "ktot");
81  edzq = (double*) allocateMemory(l1rec->npix *nbands * sizeof(double), "edzq");
82  iparb = (float*) allocateMemory(l1rec->npix * sizeof(float), "iparb");
83 
84  edt = (double*) allocateMemory( timeStepNum*nbands * sizeof(double), "edt");
85  solTimeArr = (double*) allocateMemory(timeStepNum * sizeof(double), "solTimeArr");
86  solzArr = (double*) allocateMemory(timeStepNum* sizeof(double), "solzArr");
87  partemp = (double*) allocateMemory(timeStepNum * sizeof(double), "partemp");
88  parb = (float*) allocateMemory(l1rec->npix * sizeof(float), "iparb");
89 
90 }
91 
92 /* ---------------------------------------------------------------------- */
93 /* calc_isleap() - calculates if year is leap or not */
94 /* ---------------------------------------------------------------------- */
95 int cacl_isleap(int year) {
96 
97  int isleap;
98 
99  //test if it is a leap year
100  if (year%4 == 0 ) {
101  if (year%100 == 0) {
102  if (year%400 == 0) {
103  isleap = 1;
104  } else {
105  isleap = 0;
106  }
107  } else {
108  isleap= 1;
109  }
110  } else {
111  isleap = 0;
112  }
113 
114  return isleap;
115 }
116 
117 /* ---------------------------------------------------------------------- */
118 /* calc_solz_t() - calculates solar zenith angle for given solar time */
119 /* ---------------------------------------------------------------------- */
120 double calc_solz_t(l2str *l2rec, int ip, double soltime) {
121 
122  l1str *l1rec = l2rec->l1rec;
123  float lat_rad = l1rec->lat[ip] * M_PI/180.; //latitude in radians
124 
125  float date_angle; //date angle
126  float year_len; //number of days in a year
127  double soldecl; //solar declination angle
128  double solz_t; //solar zenith angle for given solar time
129 
130  int16_t year, day;
131  double sec;
132  //Get year, day, and second from scantime
133  unix2yds(l2rec->l1rec->scantime, &year, &day, &sec);
134 
135 
136  if (cacl_isleap(year)){
137  year_len=366.;
138  } else {
139  year_len=365.;
140  }
141 
142  //compute date angle for overpass
143  date_angle = 2.*M_PI * (day-1) / year_len;
144 
145  //Compute solar declination angle
146  soldecl = 0.006918 - 0.399912*cos(date_angle) + 0.070257*sin(date_angle)
147  - 0.006758*cos(2.*date_angle) + 0.000907*sin(2.*date_angle)
148  -0.002697*cos(3.*date_angle) +0.00148*sin(3.*date_angle);
149 
150  solz_t = acos( sin(lat_rad)*sin(soldecl) + cos(lat_rad)*cos(soldecl)*cos(soltime) );
151 
152  return solz_t;
153 
154 }
155 
156 /* ------------------------------------------------------------------------ */
157 /* calc_solz_t_array() - calculates solar zenith angle for given solar time */
158 /* ------------------------------------------------------------------------ */
159 void calc_solz_t_array(l2str *l2rec, int ip) {
160 
161  l1str *l1rec = l2rec->l1rec;
162  float lat_rad = l1rec->lat[ip] * M_PI/180.; //latitude in radians
163 
164  int ix;
165  float date_angle; //date angle
166  float year_len; //number of days in a year
167  double soldecl; //solar declination angle
168  double sunrise;
169  double solTimeAngleStep;
170  double solTimeTemp;
171  int16_t year, day;
172  double sec;
173 
174  //Get year, day, and second from scantime
175  unix2yds(l2rec->l1rec->scantime, &year, &day, &sec);
176 
177  if (cacl_isleap(year)){
178  year_len=366.;
179  } else {
180  year_len=365.;
181  }
182 
183  //compute date angle for overpass
184  date_angle = 2.*M_PI * (day-1.) / year_len;
185 
186  //Compute solar declination angle
187  soldecl = 0.006918 - 0.399912*cos(date_angle) + 0.070257*sin(date_angle)
188  - 0.006758*cos(2.*date_angle) + 0.000907*sin(2.*date_angle)
189  -0.002697*cos(3.*date_angle) +0.00148*sin(3.*date_angle);
190 
191  //compute sunrise time angle
192  sunrise = -1.*acos(-1.*tan(soldecl)*tan(lat_rad)); //convention midday at 0
193 
194  //Create angle array from sunrise to solar noon
195  solTimeAngleStep = (0.0 - sunrise)/(timeStepNum) ;
196 
197  //What is the solar angle step in seconds, this is needed for daily PAR integration
198  solStepSec = (24.*60.*60.)*solTimeAngleStep/(2.*M_PI);
199 
200  //Create an array of solar time angles from sunrise to midday
201  solTimeArr[0] = 0.0;
202  solzArr[0]= acos( sin(lat_rad)*sin(soldecl) + cos(lat_rad)*cos(soldecl)*cos(solTimeArr[0]) );
203 
204  for (ix = 1; ix < timeStepNum; ix++) {
205 
206  solTimeTemp = solTimeArr[ix-1] - solTimeAngleStep;
207  solTimeArr[ix] = solTimeTemp;
208 
209  //Calculate solar zenith angle from sunrise to solar noon
210  solzArr[ix]= acos( sin(lat_rad)*sin(soldecl) + cos(lat_rad)*cos(soldecl)*cos(solTimeArr[ix]) );
211 
212  }
213 }
214 
215 /* ---------------------------------------------------------------------- */
216 /* calc_benthic_irrad() - calculates instantaneous benthic PAR */
217 /* ---------------------------------------------------------------------- */
218 double calc_benthic_ipar(l2str *l2rec, int ip, double z, double solza) {
219 
220  l1str *l1rec = l2rec->l1rec;
221  int32_t nbands = l1rec->l1file->nbands;
222 
223  float *wave = l1rec->l1file->fwave; //wavelength array
224 
225  int qcFlag = -1;
226 
227  float iparbtemp;
228 
229  int iw, ipb, ipb1, ipb2;
230  float rho_dir; //direct beam reflectance
231  float rho_dif = 0.066; //diffuse reflectance for uniform overcast sky
232  float nw = 1.341; //refractive index of water
233  float solzw; //Sub-surface solar zenith angle
234  float trans; //transmittance coefficient
235  float cloud = 0; //proportion of cloud cover
236 
237  //Values taken from Reinart et al (1998), JGR, 103(C4), 7749-7752
238  double h = 6.6255E-34; //Planck's constant J s
239  double c = 2.9979E8; //Speed of light m /s
240  //double umole = 6.022E17; //micromole photons / (m^2 s)
241  double mole = 6.022E23; //mole photons / (m^2 s)
242  double nmtom = 1E-9; //convert nm to m
243  double quanta = nmtom/(mole*h*c); //quanta energy conversion factor umol / (s W nm)
244 
245 
246  //compute reflectance of the direct (solar) beam
247  if ( solza != 0.0 ) {
248  solzw = asin(sin(solza)/nw);
249  rho_dir = 0.5* ( pow( (sin(solza - solzw)/sin(solza + solzw)), 2) +
250  pow( (tan(solza - solzw)/tan(solza + solzw)), 2) );
251  } else {
252  rho_dir = pow( ((nw - 1.)/(nw + 1.)), 2);
253  }
254 
255  //Assume a clear, non-overcast sky & calculate transmittance coefficient.
256  //However, I have included diffuse reflectance in case we modify this in
257  //future.
258  trans = (1.0 - rho_dir)*( 1.0 - cloud ) + (1.0 - rho_dif)*cloud;
259 
260 
261  for (iw = 0; iw < nbandVis; iw++) {
262 
263  ipb = ip*nbands+iw;
264 
265  //Compute incident above-water down-welling irradiance W /m^2/nm
266  //**Normalize by cos(solz) for Ed where sun is directly overhead**
267  //Note 1: the factor of 0.01 converts from uW/cm^2/nm to W/m^2/nm
268  //Note 2: to scale to Ed at specific time, you could multiply by cos(solz)
269  //If sun is directly overhead, cos(0) = 1.
270  ed[ipb] = l1rec->Fo[iw]* l1rec->tg_sol[ipb] * l1rec->t_sol[ipb] *cos(solza)* 0.01 ;
271 
272  //Compute sub-surface down-welling irradiance
273  //Convert to photon flux units of umol photos / (m^2 s)
274  edsubq[ipb] = quanta*wave[iw]*trans*ed[ipb];
275 
276  //Compute spectral diffuse attenuation coefficient following
277  //Lee et al. 2005
278  if (l1rec->mask[ip] || l2rec->a[ipb] <= 0.0 || l2rec->bb[ipb] <= 0.0 ) {
279  kdtot[ipb] = badval;
280  edzq[ipb] = badval;
281 
282  } else {
283  //Compute diffuse attenuation coefficient using Lee et al (2005)
284  kdtot[ipb] = (1.0 + 0.005 * (solza*180./M_PI))
285  * l2rec->a[ipb] + 4.18
286  * (1.0 - 0.52* exp(-10.18* l2rec->a[ipb]))
287  * l2rec->bb[ipb];
288 
289  //Check range of kd values
290  if (kdtot[ipb] > KD_MAX) {
291  kdtot[ipb] = KD_MAX;
292  l1rec->flags[ip] |= PRODWARN;
293  } else
294  if (kdtot[ipb] < KD_MIN) {
295  kdtot[ipb] = KD_MIN;
296  l1rec->flags[ip] |= PRODWARN;
297  }
298 
299  //Spectral photon flux the seafloor in unit of mol photon / (m^2 um)
300  edzq[ipb] = edsubq[ipb]*exp(-z*kdtot[ipb]);
301 
302  }
303  }
304 
305  //Integrate over band using trapezoid rule
306  iparbtemp = 0.0;
307 
308  for (iw = 0; iw < nbandVis-1 ; iw++) {
309 
310  ipb1 = ip*nbands+iw;
311  ipb2 = ip*nbands + (iw+1);
312 
313  if (l1rec->mask[ip] || edzq[ipb1] < 0 || edzq[ipb2] < 0 ) {
314  qcFlag = 1;
315  l1rec->flags[ip] |= PRODFAIL;
316  break;
317 
318  } else {
319  qcFlag = 0;
320  iparbtemp += 0.5*(wave[iw+1] - wave[iw])*(edzq[ipb2]+edzq[ipb1]);
321  }
322  }
323 
324  if (qcFlag) {
325  return BAD_FLT;
326  } else {
327  return iparbtemp;
328 
329  }
330 
331 }
332 
333 /* ---------------------------------------------------------------------- */
334 /* calc_benthic_par() - calculates daily benthic PAR */
335 /* ---------------------------------------------------------------------- */
336 double calc_benthic_par(l2str *l2rec, int ip, double z) {
337 
338  l1str *l1rec = l2rec->l1rec;
339 
340  double parbtemp;
341  double ipartemp;
342  double solztemp;
343  int qcFlag ,ix;
344 
345  //calculate solar zenith array
346  calc_solz_t_array(l2rec, ip);
347 
348  //Calculate instantaneous benthic par from sunrise to solar noon
349  for (ix = 0; ix < timeStepNum; ix++) {
350  solztemp = solzArr[ix];
351  ipartemp = calc_benthic_ipar(l2rec, ip, z, solztemp);
352  partemp[ix]= ipartemp;
353  }
354 
355  qcFlag = 0;
356  parbtemp = 0.0;
357  for (ix = 0; ix < timeStepNum-1 ; ix++) {
358 
359  //Check for bad values and flags
360  if (l1rec->mask[ip] || partemp[ix] < 0 ) {
361  qcFlag = 1;
362  l1rec->flags[ip] |= PRODFAIL;
363  break;
364 
365  } else {
366  qcFlag = 0;
367  parbtemp += 0.5*solStepSec*(partemp[ix+1]+partemp[ix]);
368  }
369  }
370 
371  if (qcFlag) {
372  return BAD_FLT;
373  } else {
374  //Multiply by two as we integrated over half the day only.
375  //We assume the solar elevation is symmetric about noon.
376  return 2.*parbtemp;
377  }
378 }
379 
380 /* ---------------------------------------------------------------------- */
381 /* run_bpar() - calculates run benthic par algorithm */
382 /* ---------------------------------------------------------------------- */
383 void run_bpar(l2str *l2rec) {
384 
385  l1str *l1rec = l2rec->l1rec;
386  filehandle *l1file = l1rec->l1file;
387  static int32_t bparScanNum = 1;
388 
389  double depth; //water-column depth for a given pixel
390  double solz_iparb; //zenith angle at pixel's solar noon
391  int ip;
392 
393 
394  if ( bparScanNum) {
395 
396  nbandVis = rdsensorinfo(l1file->sensorID, l1_input->evalmask,
397  "NbandsVIS", NULL);
398 
399  /*Allocate memory*/
400  bpar_init(l2rec);
401 
402  if (input->iop_opt == 0) {
403  printf("\n");
404  printf("bPAR model requires iop model selection. Using SWIM model (iop_opt=8).\n");
405  input->iop_opt=IOPSWIM;
406  }
407 
408  if (input->bpar_validate_opt == 0) {
409  printf("bPAR model validation option off. Compute iparb for solar noon. \n");
410  } else if (input->bpar_validate_opt == 1) {
411  printf("bPAR model option validation on. Compute ipbar using overpass solar zenith.\n");
412  } else {
413  fprintf(stderr,
414  "-E- %s line %d: invalid input value for bpar_validate_opt: \"%d\".\n",
415  __FILE__, __LINE__,input->bpar_validate_opt);
416  exit(1);
417  }
418 
419  if (input->bpar_elev_opt == 0) {
420  printf("bPAR model using bathymetry raster file. \n\n");
421  } if (input->bpar_elev_opt == 1) {
422  printf("bPAR model using user-supplied value of %f m for water depth. \n\n", input->bpar_elev_value);
423  } else if (input->bpar_elev_opt !=0 && input->bpar_elev_opt !=1) {
424  fprintf(stderr,
425  "-E- %s line %d: invalid input value for bpar_elev_opt: \"%d\".\n\n",
426  __FILE__, __LINE__,input->bpar_elev_opt);
427  exit(1);
428  }
429  bparScanNum = 0;
430 
431  }
432 
433  //Get iops - recommend use of shallow water IOP model for opticaly shallow
434  //waters. SWIM is iop_opt=8
435  get_iops(l2rec,input->iop_opt);
436 
437  /*iterate over pixels*/
438  for (ip = 0; ip < l1rec->npix; ip++) {
439 
440 
441  if (input->bpar_elev_opt==0) {
442  depth = 0.0 - l1rec->dem[ip];
443  } else if (input->bpar_elev_opt==1) {
444  depth = input->bpar_elev_value;
445  }
446 
447  iparb[ip] = BAD_FLT;
448  parb[ip] = BAD_FLT;
449 
450  //Check if we are on the continental shelf (300m conservative)
451  //Ignore deep water pixels
452  if (depth > 300.) {
453 
454  //If water deeper than 300m set as bad value
455  iparb[ip] = BAD_FLT;;
456  parb[ip] = BAD_FLT;
457 
458  } else {
459 
460  if (input->bpar_validate_opt==0) {
461  solz_iparb = calc_solz_t(l2rec, ip, 0.0);
462  }
463 
464  if (input->bpar_validate_opt==1) {
465  solz_iparb = l1rec->solz[ip] * M_PI/180;
466  }
467 
468  iparb[ip] = calc_benthic_ipar(l2rec, ip, depth, solz_iparb);
469  parb[ip] = calc_benthic_par(l2rec,ip, depth);
470 
471  }
472 
473  }
474 
475  LastRecNum = l1rec->iscan;
476 
477 }
478 
479 /* -------------------------------------------------------------------- ------*/
480 /* get_ibpar() - returns requested benthic irradiance product for full scanline*/
481 /* ---------------------------------------------------------------------------*/
482 void get_bpar(l2str *l2rec, l2prodstr *p, float prod[]) {
483 
484  l1str *l1rec = l2rec->l1rec;
485  int ip;
486 
487  if (!bpar_ran(l1rec->iscan))
488  run_bpar(l2rec);
489 
490  for (ip = 0; ip < l2rec->l1rec->npix; ip++) {
491 
492  switch (p->cat_ix) {
493  case CAT_iparb:
494  prod[ip] = (float) iparb[ip];
495  break;
496  case CAT_parb:
497  prod[ip] = (float) parb[ip];
498  break;
499  default:
500  printf("Error: %s : Unknown product specifier: %d\n", __FILE__, p->cat_ix);
501  exit(FATAL_ERROR);
502  break;
503  }
504  }
505 
506 }
void calc_solz_t_array(l2str *l2rec, int ip)
Definition: get_bpar.c:159
int32 l1file(int32 sdfid, int32 *nsamp, int32 *nscans, int16 *dtynum)
Definition: l1stat_chk.c:586
void run_bpar(l2str *l2rec)
Definition: get_bpar.c:383
#define KD_MAX
Definition: get_bpar.c:38
int bpar_ran(int recnum)
Definition: get_bpar.c:62
int32_t day
double calc_solz_t(l2str *l2rec, int ip, double soltime)
Definition: get_bpar.c:120
void * allocateMemory(size_t numBytes, const char *name)
Definition: allocateMemory.c:7
#define NULL
Definition: decode_rs.h:63
read l1rec
@ PRODWARN
float h[MODELMAX]
Definition: atrem_corl1.h:131
void bpar_init(l2str *l2rec)
Definition: get_bpar.c:73
double calc_benthic_par(l2str *l2rec, int ip, double z)
Definition: get_bpar.c:336
double calc_benthic_ipar(l2str *l2rec, int ip, double z, double solza)
Definition: get_bpar.c:218
void get_bpar(l2str *l2rec, l2prodstr *p, float prod[])
Definition: get_bpar.c:482
instr * input
character(len=1000) if
Definition: names.f90:13
int cacl_isleap(int year)
Definition: get_bpar.c:95
read recnum
void get_iops(l2str *l2rec, int32_t iop_opt)
Definition: convl12.c:113
l1_input_t * l1_input
Definition: l1_options.c:9
#define FATAL_ERROR
Definition: swl0_parms.h:5
#define M_PI
Definition: pml_iop.h:15
void unix2yds(double usec, short *year, short *day, double *secs)
int isleap(int year)
Definition: isleap.c:3
#define CAT_iparb
Definition: l2prod.h:287
int32_t timeStepNum
Definition: get_bpar.c:43
#define BAD_FLT
Definition: jplaeriallib.h:19
int32_t nbands
#define CAT_parb
Definition: l2prod.h:288
int32_t rdsensorinfo(int32_t, int32_t, const char *, void **)
Definition: rdsensorinfo.c:69
no change in intended resolving MODur00064 Corrected handling of bad ephemeris attitude resolving resolving GSFcd00179 Corrected handling of fill values for[Sensor|Solar][Zenith|Azimuth] resolving MODxl01751 Changed to validate LUT version against a value retrieved from the resolving MODxl02056 Changed to calculate Solar Diffuser angles without adjustment for estimated post launch changes in the MODIS orientation relative to incidentally resolving defects MODxl01766 Also resolves MODxl01947 Changed to ignore fill values in SCI_ABNORM and SCI_STATE rather than treating them as resolving MODxl01780 Changed to use spacecraft ancillary data to recognise when the mirror encoder data is being set by side A or side B and to change calculations accordingly This removes the need for seperate LUTs for Side A and Side B data it makes the new LUTs incompatible with older versions of the and vice versa Also resolves MODxl01685 A more robust GRing algorithm is being which will create a non default GRing anytime there s even a single geolocated pixel in a granule Removed obsolete messages from seed as required for compatibility with version of the SDP toolkit Corrected test output file names to end in per delivery and then split off a new MYD_PR03 pcf file for Aqua Added AssociatedPlatformInstrumentSensor to the inventory metadata in MOD01 mcf and MOD03 mcf Created new versions named MYD01 mcf and MYD03 where AssociatedPlatformShortName is rather than Terra The program itself has been changed to read the Satellite Instrument validate it against the input L1A and LUT and to use it determine the correct files to retrieve the ephemeris and attitude data from Changed to produce a LocalGranuleID starting with MYD03 if run on Aqua data Added the Scan Type file attribute to the Geolocation copied from the L1A and attitude_angels to radians rather than degrees The accumulation of Cumulated gflags was moved from GEO_validate_earth_location c to GEO_locate_one_scan c
Definition: HISTORY.txt:464
@ PRODFAIL
#define KD_MIN
Definition: get_bpar.c:39
float p[MODELMAX]
Definition: atrem_corl1.h:131
#define IOPSWIM
Definition: l12_parms.h:75